Merge branch 'master' into fs_QuadricEdgeCollapse

This commit is contained in:
Filip Sykala 2021-08-16 11:56:56 +02:00
commit b90ca142a5
116 changed files with 3328 additions and 1335 deletions

View file

@ -223,10 +223,13 @@ std::vector<std::string> ConfigOptionDef::cli_args(const std::string &key) const
{
std::vector<std::string> args;
if (this->cli != ConfigOptionDef::nocli) {
std::string cli = this->cli.substr(0, this->cli.find("="));
boost::trim_right_if(cli, boost::is_any_of("!"));
const std::string &cli = this->cli;
//FIXME What was that for? Check the "readline" documentation.
// Neither '=' nor '!' is used in any of the cli parameters currently defined by PrusaSlicer.
// std::string cli = this->cli.substr(0, this->cli.find("="));
// boost::trim_right_if(cli, boost::is_any_of("!"));
if (cli.empty()) {
// Add the key
// Convert an option key to CLI argument by replacing underscores with dashes.
std::string opt = key;
boost::replace_all(opt, "_", "-");
args.emplace_back(std::move(opt));
@ -245,7 +248,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coPercents: return new ConfigOptionPercentsNullable();
case coFloatsOrPercents: return new ConfigOptionFloatsOrPercentsNullable();
case coBools: return new ConfigOptionBoolsNullable();
default: throw Slic3r::RuntimeError(std::string("Unknown option type for nullable option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for nullable option ") + this->label);
}
} else {
switch (this->type) {
@ -266,7 +269,7 @@ ConfigOption* ConfigOptionDef::create_empty_option() const
case coBool: return new ConfigOptionBool();
case coBools: return new ConfigOptionBools();
case coEnum: return new ConfigOptionEnumGeneric(this->enum_keys_map);
default: throw Slic3r::RuntimeError(std::string("Unknown option type for option ") + this->label);
default: throw ConfigurationError(std::string("Unknown option type for option ") + this->label);
}
}
}
@ -494,7 +497,7 @@ bool ConfigBase::set_deserialize_nothrow(const t_config_option_key &opt_key_src,
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, substitutions_ctxt, append))
throw BadOptionTypeException(format("ConfigBase::set_deserialize() failed for parameter \"%1%\", value \"%2%\"", opt_key_src, value_src));
throw BadOptionValueException(format("Invalid value provided for parameter %1%: %2%", opt_key_src, value_src));
}
void ConfigBase::set_deserialize(std::initializer_list<SetDeserializeItem> items, ConfigSubstitutionContext& substitutions_ctxt)
@ -539,26 +542,50 @@ bool ConfigBase::set_deserialize_raw(const t_config_option_key &opt_key_src, con
ConfigOption *opt = this->option(opt_key, true);
assert(opt != nullptr);
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);
bool success = false;
bool substituted = false;
if (optdef->type == coBools && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable) {
//FIXME Special handling of vectors of bools, quick and not so dirty solution before PrusaSlicer 2.3.2 release.
bool nullable = opt->nullable();
ConfigHelpers::DeserializationSubstitution default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToFalse;
if (optdef->default_value) {
// Default value for vectors of booleans used in a "per extruder" context, thus the default contains just a single value.
assert(dynamic_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get()));
auto &values = static_cast<const ConfigOptionVector<unsigned char>*>(optdef->default_value.get())->values;
if (values.size() == 1 && values.front() == 1)
default_value = ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
}
auto result = nullable ?
static_cast<ConfigOptionBoolsNullable*>(opt)->deserialize_with_substitutions(value, append, default_value) :
static_cast<ConfigOptionBools*>(opt)->deserialize_with_substitutions(value, append, default_value);
success = result != ConfigHelpers::DeserializationResult::Failed;
substituted = result == ConfigHelpers::DeserializationResult::Substituted;
} else {
success = opt->deserialize(value, append);
if (! success && substitutions_ctxt.rule != ForwardCompatibilitySubstitutionRule::Disable &&
// Only allow substitutions of an enum value by another enum value or a boolean value with an enum value.
// That means, we expect enum values being added in the future and possibly booleans being converted to enums.
(optdef->type == coEnum || optdef->type == coBool) && ConfigHelpers::looks_like_enum_value(value)) {
// Deserialize failed, try to substitute with a default value.
assert(substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable || substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSilent);
if (optdef->type == coBool)
static_cast<ConfigOptionBool*>(opt)->value = ConfigHelpers::enum_looks_like_true_value(value);
else
// Just use the default of the option.
opt->set(optdef->default_value.get());
success = true;
substituted = true;
}
}
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;
if (substituted && (substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::Enable ||
substitutions_ctxt.rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)) {
// Log the substitution.
ConfigSubstitution config_substitution;
config_substitution.opt_def = optdef;
config_substitution.old_value = value;
config_substitution.new_value = ConfigOptionUniquePtr(opt->clone());
substitutions_ctxt.substitutions.emplace_back(std::move(config_substitution));
}
return success;
}
@ -585,7 +612,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key) const
return opt_def->ratio_over.empty() ? 0. :
static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(this->get_abs_value(opt_def->ratio_over));
}
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
throw ConfigurationError("ConfigBase::get_abs_value(): Not a valid option type for get_abs_value()");
}
// Return an absolute value of a possibly relative config variable.
@ -596,7 +623,7 @@ double ConfigBase::get_abs_value(const t_config_option_key &opt_key, double rati
const ConfigOption *raw_opt = this->option(opt_key);
assert(raw_opt != nullptr);
if (raw_opt->type() != coFloatOrPercent)
throw Slic3r::RuntimeError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
throw ConfigurationError("ConfigBase::get_abs_value(): opt_key is not of coFloatOrPercent");
// Compute absolute value.
return static_cast<const ConfigOptionFloatOrPercent*>(raw_opt)->get_abs_value(ratio_over);
}
@ -622,18 +649,71 @@ void ConfigBase::setenv_() const
ConfigSubstitutions ConfigBase::load(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
return is_gcode_file(file) ?
this->load_from_gcode_file(file, true /* check header */, compatibility_rule) :
this->load_from_gcode_file(file, compatibility_rule) :
this->load_from_ini(file, compatibility_rule);
}
ConfigSubstitutions ConfigBase::load_from_ini(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
try {
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
return this->load(tree, compatibility_rule);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Failed loading configuration file \"%1%\": %2%", file, e.what()));
}
}
ConfigSubstitutions ConfigBase::load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
boost::property_tree::ptree tree;
boost::nowide::ifstream ifs(file);
boost::property_tree::read_ini(ifs, tree);
std::istringstream iss(data);
boost::property_tree::read_ini(iss, tree);
return this->load(tree, compatibility_rule);
}
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
ConfigSubstitutions ConfigBase::load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Convert the "data" string into INI format by removing the semi-colons at the start of a line.
// Also the "; generated by PrusaSlicer ..." comment line will be removed.
size_t j = 0;
for (size_t i = 0; i < data.size();)
if (i == 0 || data[i] == '\n') {
// Start of a line.
if (i != 0) {
// Consume LF.
assert(data[i] == '\n');
// Don't keep empty lines.
if (j > 0 && data[j - 1] != '\n')
data[j ++] = data[i];
++ i;
}
// Skip all leading spaces;
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
// Skip the semicolon (comment indicator).
if (i < data.size() && data[i] == ';')
++ i;
// Skip all leading spaces after semicolon.
for (; i < data.size() && (data[i] == ' ' || data[i] == '\t'); ++ i) ;
if (strncmp(data.data() + i, "generated by ", 13) == 0) {
// Skip the "; generated by ..." line.
for (; i < data.size() && data[i] != '\n'; ++ i);
}
} else if (data[i] == '\r' && i + 1 < data.size() && data[i + 1] == '\n') {
// Skip CR.
++ i;
} else {
// Consume the rest of the data.
data[j ++] = data[i ++];
}
data.erase(data.begin() + j, data.end());
return this->load_from_ini_string(data, compatibility_rule);
}
ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
@ -648,37 +728,8 @@ ConfigSubstitutions ConfigBase::load(const boost::property_tree::ptree &tree, Fo
return std::move(substitutions_ctxt.substitutions);
}
// Load the config keys from the tail of a G-code file.
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);
if (check_header) {
const char slic3r_gcode_header[] = "; generated by Slic3r ";
const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string firstline;
std::getline(ifs, firstline);
if (strncmp(slic3r_gcode_header, firstline.c_str(), strlen(slic3r_gcode_header)) != 0 &&
strncmp(prusaslicer_gcode_header, firstline.c_str(), strlen(prusaslicer_gcode_header)) != 0)
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
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, ConfigSubstitutionContext& substitutions)
static inline size_t load_from_gcode_string_legacy(ConfigBase &config, const char *str, ConfigSubstitutionContext &substitutions)
{
if (str == nullptr)
return 0;
@ -701,7 +752,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
if (end - (++ start) < 10 || start[0] != ';' || start[1] != ' ')
break;
const char *key = start + 2;
if (!(*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z'))
if (!((*key >= 'a' && *key <= 'z') || (*key >= 'A' && *key <= 'Z')))
// A key must start with a letter.
break;
const char *sep = key;
@ -723,7 +774,7 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
if (key == nullptr)
break;
try {
this->set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
config.set_deserialize(std::string(key, key_end), std::string(value, end), substitutions);
++num_key_value_pairs;
}
catch (UnknownOptionException & /* e */) {
@ -732,7 +783,175 @@ size_t ConfigBase::load_from_gcode_string(const char* str, ConfigSubstitutionCon
end = start;
}
return num_key_value_pairs;
return num_key_value_pairs;
}
// Reading a config from G-code back to front for performance reasons: We don't want to scan
// hundreds of MB file for a short config block, which we expect to find at the end of the G-code.
class ReverseLineReader
{
public:
using pos_type = boost::nowide::ifstream::pos_type;
// Stop at file_start
ReverseLineReader(boost::nowide::ifstream &ifs, pos_type file_start) : m_ifs(ifs), m_file_start(file_start)
{
m_ifs.seekg(0, m_ifs.end);
m_file_pos = m_ifs.tellg();
m_block.assign(m_block_size, 0);
}
bool getline(std::string &out) {
out.clear();
for (;;) {
if (m_block_len == 0) {
// Read the next block.
m_block_len = size_t(std::min<std::fstream::pos_type>(m_block_size, m_file_pos - m_file_start));
if (m_block_len == 0)
return false;
m_file_pos -= m_block_len;
m_ifs.seekg(m_file_pos, m_ifs.beg);
if (! m_ifs.read(m_block.data(), m_block_len))
return false;
}
assert(m_block_len > 0);
// Non-empty buffer. Find another LF.
int i = int(m_block_len) - 1;
for (; i >= 0; -- i)
if (m_block[i] == '\n')
break;
// i is position of LF or -1 if not found.
if (i == -1) {
// LF not found. Just make a backup of the buffer and continue.
out.insert(out.begin(), m_block.begin(), m_block.begin() + m_block_len);
m_block_len = 0;
} else {
assert(i >= 0);
// Copy new line to the output. It may be empty.
out.insert(out.begin(), m_block.begin() + i + 1, m_block.begin() + m_block_len);
// Block length without the newline.
m_block_len = i;
// Remove CRLF from the end of the block.
if (m_block_len > 0 && m_block[m_block_len - 1] == '\r')
-- m_block_len;
return true;
}
}
assert(false);
return false;
}
private:
boost::nowide::ifstream &m_ifs;
std::vector<char> m_block;
size_t m_block_size = 65536;
size_t m_block_len = 0;
pos_type m_file_start;
pos_type m_file_pos = 0;
};
// Load the config keys from the tail of a G-code file.
ConfigSubstitutions ConfigBase::load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// Read a 64k block from the end of the G-code.
boost::nowide::ifstream ifs(file);
// Look for Slic3r or PrusaSlicer header.
// Look for the header across the whole file as the G-code may have been extended at the start by a post-processing script or the user.
bool has_delimiters = false;
{
static constexpr const char slic3r_gcode_header[] = "; generated by Slic3r ";
static constexpr const char prusaslicer_gcode_header[] = "; generated by PrusaSlicer ";
std::string header;
bool header_found = false;
while (std::getline(ifs, header)) {
if (strncmp(slic3r_gcode_header, header.c_str(), strlen(slic3r_gcode_header)) == 0) {
header_found = true;
break;
} else if (strncmp(prusaslicer_gcode_header, header.c_str(), strlen(prusaslicer_gcode_header)) == 0) {
// Parse PrusaSlicer version.
size_t i = strlen(prusaslicer_gcode_header);
for (; i < header.size() && header[i] == ' '; ++ i) ;
size_t j = i;
for (; j < header.size() && header[j] != ' '; ++ j) ;
try {
Semver semver(header.substr(i, j - i));
has_delimiters = semver >= Semver(2, 4, 0, nullptr, "alpha0");
} catch (const RuntimeError &) {
}
header_found = true;
break;
}
}
if (! header_found)
throw Slic3r::RuntimeError("Not a PrusaSlicer / Slic3r PE generated g-code.");
}
auto header_end_pos = ifs.tellg();
ConfigSubstitutionContext substitutions_ctxt(compatibility_rule);
size_t key_value_pairs = 0;
if (has_delimiters)
{
// PrusaSlicer starting with 2.4.0-alpha0 delimits the config section stored into G-code with
// ; prusaslicer_config = begin
// ...
// ; prusaslicer_config = end
// The begin / end tags look like any other key / value pairs on purpose to be compatible with older G-code viewer.
// Read the file in reverse line by line.
ReverseLineReader reader(ifs, header_end_pos);
// Read the G-code file by 64k blocks back to front.
bool begin_found = false;
bool end_found = false;
std::string line;
while (reader.getline(line))
if (line == "; prusaslicer_config = end") {
end_found = true;
break;
}
if (! end_found)
throw Slic3r::RuntimeError(format("Configuration block closing tag \"; prusaslicer_config = end\" not found when reading %1%", file));
std::string key, value;
while (reader.getline(line)) {
if (line == "; prusaslicer_config = begin") {
begin_found = true;
break;
}
// line should be a valid key = value pair.
auto pos = line.find('=');
if (pos != std::string::npos && pos > 1 && line.front() == ';') {
key = line.substr(1, pos - 1);
value = line.substr(pos + 1);
boost::trim(key);
boost::trim(value);
try {
this->set_deserialize(key, value, substitutions_ctxt);
++ key_value_pairs;
} catch (UnknownOptionException & /* e */) {
// ignore
}
}
}
if (! begin_found)
throw Slic3r::RuntimeError(format("Configuration block opening tag \"; prusaslicer_config = begin\" not found when reading %1%", file));
}
else
{
// Slic3r or PrusaSlicer older than 2.4.0-alpha0 do not emit any delimiter.
// Try a heuristics reading the G-code from back.
ifs.seekg(0, ifs.end);
auto file_length = ifs.tellg();
auto data_length = std::min<std::fstream::pos_type>(65535, file_length - header_end_pos);
ifs.seekg(file_length - data_length, ifs.beg);
std::vector<char> data(size_t(data_length) + 1, 0);
ifs.read(data.data(), data_length);
ifs.close();
key_value_pairs = load_from_gcode_string_legacy(*this, 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);
}
void ConfigBase::save(const std::string &file) const
@ -803,7 +1022,7 @@ ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key, bool cre
throw NoDefinitionException(opt_key);
const ConfigOptionDef *optdef = def->get(opt_key);
if (optdef == nullptr)
// throw Slic3r::RuntimeError(std::string("Invalid option name: ") + opt_key);
// throw ConfigurationError(std::string("Invalid option name: ") + opt_key);
// Let the parent decide what to do if the opt_key is not defined by this->def().
return nullptr;
ConfigOption *opt = optdef->create_default_option();
@ -817,22 +1036,12 @@ const ConfigOption* DynamicConfig::optptr(const t_config_option_key &opt_key) co
return (it == options.end()) ? nullptr : it->second.get();
}
void DynamicConfig::read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys)
{
std::vector<const char*> args;
// push a bogus executable name (argv[0])
args.emplace_back("");
for (size_t i = 0; i < tokens.size(); ++ i)
args.emplace_back(tokens[i].c_str());
this->read_cli(int(args.size()), args.data(), extra, keys);
}
bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys)
{
// cache the CLI option => opt_key mapping
std::map<std::string,std::string> opts;
for (const auto &oit : this->def()->options)
for (auto t : oit.second.cli_args(oit.first))
for (const std::string &t : oit.second.cli_args(oit.first))
opts[t] = oit.first;
bool parse_options = true;
@ -854,14 +1063,8 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
parse_options = false;
continue;
}
// Remove leading dashes
boost::trim_left_if(token, boost::is_any_of("-"));
// Remove the "no-" prefix used to negate boolean options.
bool no = false;
if (boost::starts_with(token, "no-")) {
no = true;
boost::replace_first(token, "no-", "");
}
// Remove leading dashes (one or two).
token.erase(token.begin(), token.begin() + (boost::starts_with(token, "--") ? 2 : 1));
// Read value when supplied in the --key=value form.
std::string value;
{
@ -871,54 +1074,45 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
token.erase(equals_pos);
}
}
// Look for the cli -> option mapping.
const auto it = opts.find(token);
auto it = opts.find(token);
bool no = false;
if (it == opts.end()) {
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
return false;
// Remove the "no-" prefix used to negate boolean options.
std::string yes_token;
if (boost::starts_with(token, "no-")) {
yes_token = token.substr(3);
it = opts.find(yes_token);
no = true;
}
if (it == opts.end()) {
boost::nowide::cerr << "Unknown option --" << token.c_str() << std::endl;
return false;
}
if (no)
token = yes_token;
}
const t_config_option_key opt_key = it->second;
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
const t_config_option_key &opt_key = it->second;
const ConfigOptionDef &optdef = this->def()->options.at(opt_key);
// If the option type expects a value and it was not already provided,
// look for it in the next token.
if (value.empty()) {
if (optdef.type != coBool && optdef.type != coBools) {
if (i == (argc-1)) {
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
return false;
}
value = argv[++ i];
} else {
// This is a bool or bools. The value is optional, but may still be there.
// Check if the next token can be deserialized into ConfigOptionBool.
// If it is in fact bools, it will be rejected later anyway.
if (i != argc-1) { // There is still a token to read.
ConfigOptionBool cobool;
if (cobool.deserialize(argv[i+1]))
value = argv[++i];
}
if (value.empty() && optdef.type != coBool && optdef.type != coBools) {
if (i == argc-1) {
boost::nowide::cerr << "No value supplied for --" << token.c_str() << std::endl;
return false;
}
value = argv[++ i];
}
if (no) {
if (optdef.type != coBool && optdef.type != coBools) {
boost::nowide::cerr << "Only boolean config options can be negated with --no- prefix." << std::endl;
return false;
}
else if (! value.empty()) {
assert(optdef.type == coBool || optdef.type == coBools);
if (! value.empty()) {
boost::nowide::cerr << "Boolean options negated by the --no- prefix cannot have a value." << std::endl;
return false;
}
}
if (optdef.type == coBools && ! value.empty()) {
boost::nowide::cerr << "Vector boolean options cannot have a value. Fill them in by "
"repeating them and negate by --no- prefix." << std::endl;
return false;
}
// Store the option value.
const bool existing = this->has(opt_key);
@ -934,7 +1128,7 @@ bool DynamicConfig::read_cli(int argc, const char* const argv[], t_config_option
opt_vector->clear();
// Vector values will be chained. Repeated use of a parameter will append the parameter or parameters
// to the end of the value.
if (opt_base->type() == coBools)
if (opt_base->type() == coBools && value.empty())
static_cast<ConfigOptionBools*>(opt_base)->values.push_back(!no);
else
// Deserialize any other vector value (ConfigOptionInts, Floats, Percents, Points) the same way

View file

@ -80,32 +80,77 @@ extern bool unescape_strings_cstyle(const std::string &str, std::vector<
extern std::string escape_ampersand(const std::string& str);
/// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public Slic3r::RuntimeError {
public:
UnknownOptionException() :
Slic3r::RuntimeError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("Unknown option exception: ") + opt_key) {}
namespace ConfigHelpers {
inline bool looks_like_enum_value(std::string value)
{
boost::trim(value);
if (value.empty() || value.size() > 64 || ! isalpha(value.front()))
return false;
for (const char c : value)
if (! (isalnum(c) || c == '_' || c == '-'))
return false;
return true;
}
inline bool enum_looks_like_true_value(std::string value) {
boost::trim(value);
return boost::iequals(value, "enabled") || boost::iequals(value, "on");
}
enum class DeserializationSubstitution {
Disabled,
DefaultsToFalse,
DefaultsToTrue
};
enum class DeserializationResult {
Loaded,
Substituted,
Failed,
};
};
/// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public Slic3r::RuntimeError
// Base for all exceptions thrown by the configuration layer.
class ConfigurationError : public Slic3r::RuntimeError {
public:
using RuntimeError::RuntimeError;
};
// Specialization of std::exception to indicate that an unknown config option has been encountered.
class UnknownOptionException : public ConfigurationError {
public:
UnknownOptionException() :
ConfigurationError("Unknown option exception") {}
UnknownOptionException(const std::string &opt_key) :
ConfigurationError(std::string("Unknown option exception: ") + opt_key) {}
};
// Indicate that the ConfigBase derived class does not provide config definition (the method def() returns null).
class NoDefinitionException : public ConfigurationError
{
public:
NoDefinitionException() :
Slic3r::RuntimeError("No definition exception") {}
ConfigurationError("No definition exception") {}
NoDefinitionException(const std::string &opt_key) :
Slic3r::RuntimeError(std::string("No definition exception: ") + opt_key) {}
ConfigurationError(std::string("No definition exception: ") + opt_key) {}
};
/// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public Slic3r::RuntimeError
// Indicate that an unsupported accessor was called on a config option.
class BadOptionTypeException : public ConfigurationError
{
public:
BadOptionTypeException() : Slic3r::RuntimeError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException(const char* message) : Slic3r::RuntimeError(message) {}
BadOptionTypeException() : ConfigurationError("Bad option type exception") {}
BadOptionTypeException(const std::string &message) : ConfigurationError(message) {}
BadOptionTypeException(const char* message) : ConfigurationError(message) {}
};
// Indicate that an option has been deserialized from an invalid value.
class BadOptionValueException : public ConfigurationError
{
public:
BadOptionValueException() : ConfigurationError("Bad option value exception") {}
BadOptionValueException(const std::string &message) : ConfigurationError(message) {}
BadOptionValueException(const char* message) : ConfigurationError(message) {}
};
// Type of a configuration value.
@ -166,9 +211,16 @@ enum PrinterTechnology : unsigned char
enum ForwardCompatibilitySubstitutionRule
{
// Disable susbtitution, throw exception if an option value is not recognized.
Disable,
// Enable substitution of an unknown option value with default. Log the substitution.
Enable,
// Enable substitution of an unknown option value with default. Don't log the substitution.
EnableSilent,
// Enable substitution of an unknown option value with default. Log substitutions in user profiles, don't log substitutions in system profiles.
EnableSystemSilent,
// Enable silent substitution of an unknown option value with default when loading user profiles. Throw on an unknown option value in a system profile.
EnableSilentDisableSystem,
};
class ConfigOption;
@ -252,7 +304,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionSingle: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(rhs));
this->value = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
}
@ -260,7 +312,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionSingle: Comparing incompatible types");
throw ConfigurationError("ConfigOptionSingle: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionSingle<T>*>(&rhs));
return this->value == static_cast<const ConfigOptionSingle<T>*>(&rhs)->value;
}
@ -327,7 +379,7 @@ public:
void set(const ConfigOption *rhs) override
{
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionVector<T>*>(rhs));
this->values = static_cast<const ConfigOptionVector<T>*>(rhs)->values;
}
@ -344,12 +396,12 @@ public:
if (opt->type() == this->type()) {
auto other = static_cast<const ConfigOptionVector<T>*>(opt);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set(): Assigning from an empty vector");
this->values.emplace_back(other->values.front());
} else if (opt->type() == this->scalar_type())
this->values.emplace_back(static_cast<const ConfigOptionSingle<T>*>(opt)->value);
else
throw Slic3r::RuntimeError("ConfigOptionVector::set():: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set():: Assigning an incompatible type");
}
}
@ -368,12 +420,12 @@ public:
// Assign the first value of the rhs vector.
auto other = static_cast<const ConfigOptionVector<T>*>(rhs);
if (other->values.empty())
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning from an empty vector");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning from an empty vector");
this->values[i] = other->get_at(j);
} else if (rhs->type() == this->scalar_type())
this->values[i] = static_cast<const ConfigOptionSingle<T>*>(rhs)->value;
else
throw Slic3r::RuntimeError("ConfigOptionVector::set_at(): Assigning an incompatible type");
throw ConfigurationError("ConfigOptionVector::set_at(): Assigning an incompatible type");
}
const T& get_at(size_t i) const
@ -398,9 +450,9 @@ public:
else if (n > this->values.size()) {
if (this->values.empty()) {
if (opt_default == nullptr)
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): No default value provided.");
throw ConfigurationError("ConfigOptionVector::resize(): No default value provided.");
if (opt_default->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector::resize(): Extending with an incompatible type.");
throw ConfigurationError("ConfigOptionVector::resize(): Extending with an incompatible type.");
this->values.resize(n, static_cast<const ConfigOptionVector<T>*>(opt_default)->values.front());
} else {
// Resize by duplicating the last value.
@ -417,7 +469,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector: Comparing incompatible types");
throw ConfigurationError("ConfigOptionVector: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<T>*>(&rhs));
return this->values == static_cast<const ConfigOptionVector<T>*>(&rhs)->values;
}
@ -437,9 +489,9 @@ public:
// An option overrides another option if it is not nil and not equal.
bool overriden_by(const ConfigOption *rhs) const override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.overriden_by() applied to different types.");
throw ConfigurationError("ConfigOptionVector.overriden_by() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable())
// Overridding a non-nullable object with another non-nullable object.
@ -457,9 +509,9 @@ public:
// Apply an override option, possibly a nullable one.
bool apply_override(const ConfigOption *rhs) override {
if (this->nullable())
throw Slic3r::RuntimeError("Cannot override a nullable ConfigOption.");
throw ConfigurationError("Cannot override a nullable ConfigOption.");
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionVector.apply_override() applied to different types.");
throw ConfigurationError("ConfigOptionVector.apply_override() applied to different types.");
auto rhs_vec = static_cast<const ConfigOptionVector<T>*>(rhs);
if (! rhs->nullable()) {
// Overridding a non-nullable object with another non-nullable object.
@ -550,7 +602,7 @@ public:
bool operator< (const ConfigOptionFloatsTempl &rhs) const throw() { return vectors_lower(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<double>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<double>*>(&rhs)->values);
}
@ -597,7 +649,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
double value;
@ -622,9 +674,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<double> &v1, const std::vector<double> &v2) {
if (NULLABLE) {
@ -756,7 +808,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
std::istringstream iss(item_str);
int value;
@ -773,7 +825,7 @@ private:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << v;
}
@ -963,7 +1015,7 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatOrPercent: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(&rhs));
return *this == *static_cast<const ConfigOptionFloatOrPercent*>(&rhs);
}
@ -979,7 +1031,7 @@ public:
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionFloatOrPercent: Assigning an incompatible type");
assert(dynamic_cast<const ConfigOptionFloatOrPercent*>(rhs));
*this = *static_cast<const ConfigOptionFloatOrPercent*>(rhs);
}
@ -1023,7 +1075,7 @@ public:
bool operator==(const ConfigOptionFloatsOrPercentsTempl &rhs) const throw() { return vectors_equal(this->values, rhs.values); }
bool operator==(const ConfigOption &rhs) const override {
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
throw ConfigurationError("ConfigOptionFloatsOrPercentsTempl: Comparing incompatible types");
assert(dynamic_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs));
return vectors_equal(this->values, static_cast<const ConfigOptionVector<FloatOrPercent>*>(&rhs)->values);
}
@ -1072,7 +1124,7 @@ public:
if (NULLABLE)
this->values.push_back(nil_value());
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else {
bool percent = item_str.find_first_of("%") != std::string::npos;
std::istringstream iss(item_str);
@ -1100,9 +1152,9 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
throw Slic3r::RuntimeError("Serializing invalid number");
throw ConfigurationError("Serializing invalid number");
}
static bool vectors_equal(const std::vector<FloatOrPercent> &v1, const std::vector<FloatOrPercent> &v2) {
if (NULLABLE) {
@ -1308,11 +1360,11 @@ public:
bool deserialize(const std::string &str, bool append = false) override
{
UNUSED(append);
if (str == "1" || boost::iequals(str, "enabled") || boost::iequals(str, "on")) {
if (str == "1") {
this->value = true;
return true;
}
if (str == "0" || boost::iequals(str, "disabled") || boost::iequals(str, "off")) {
if (str == "0") {
this->value = false;
return true;
}
@ -1378,24 +1430,39 @@ public:
}
return vv;
}
bool deserialize(const std::string &str, bool append = false) override
ConfigHelpers::DeserializationResult deserialize_with_substitutions(const std::string &str, bool append, ConfigHelpers::DeserializationSubstitution substitution)
{
if (! append)
this->values.clear();
std::istringstream is(str);
std::string item_str;
bool substituted = false;
while (std::getline(is, item_str, ',')) {
boost::trim(item_str);
unsigned char new_value = 0;
if (item_str == "nil") {
if (NULLABLE)
this->values.push_back(nil_value());
new_value = nil_value();
else
throw Slic3r::RuntimeError("Deserializing nil into a non-nullable object");
throw ConfigurationError("Deserializing nil into a non-nullable object");
} else if (item_str == "1") {
new_value = true;
} else if (item_str == "0") {
new_value = false;
} else if (substitution != ConfigHelpers::DeserializationSubstitution::Disabled && ConfigHelpers::looks_like_enum_value(item_str)) {
new_value = ConfigHelpers::enum_looks_like_true_value(item_str) || substitution == ConfigHelpers::DeserializationSubstitution::DefaultsToTrue;
substituted = true;
} else
this->values.push_back(item_str.compare("1") == 0);
return ConfigHelpers::DeserializationResult::Failed;
this->values.push_back(new_value);
}
return true;
return substituted ? ConfigHelpers::DeserializationResult::Substituted : ConfigHelpers::DeserializationResult::Loaded;
}
bool deserialize(const std::string &str, bool append = false) override
{
return this->deserialize_with_substitutions(str, append, ConfigHelpers::DeserializationSubstitution::Disabled) == ConfigHelpers::DeserializationResult::Loaded;
}
protected:
@ -1404,7 +1471,7 @@ protected:
if (NULLABLE)
ss << "nil";
else
throw Slic3r::RuntimeError("Serializing NaN");
throw ConfigurationError("Serializing NaN");
} else
ss << (v ? "1" : "0");
}
@ -1442,14 +1509,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnum<T>: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == (T)rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnum<T>: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnum<T>: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = (T)rhs->getInt();
}
@ -1512,14 +1579,14 @@ public:
bool operator==(const ConfigOption &rhs) const override
{
if (rhs.type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Comparing incompatible types");
throw ConfigurationError("ConfigOptionEnumGeneric: Comparing incompatible types");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
return this->value == rhs.getInt();
}
void set(const ConfigOption *rhs) override {
if (rhs->type() != this->type())
throw Slic3r::RuntimeError("ConfigOptionEnumGeneric: Assigning an incompatible type");
throw ConfigurationError("ConfigOptionEnumGeneric: Assigning an incompatible type");
// rhs could be of the following type: ConfigOptionEnumGeneric or ConfigOptionEnum<T>
this->value = rhs->getInt();
}
@ -1592,7 +1659,7 @@ public:
case coInts: { auto opt = new ConfigOptionIntsNullable(); archive(*opt); return opt; }
case coPercents: { auto opt = new ConfigOptionPercentsNullable();archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBoolsNullable(); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1611,7 +1678,7 @@ public:
case coBool: { auto opt = new ConfigOptionBool(); archive(*opt); return opt; }
case coBools: { auto opt = new ConfigOptionBools(); archive(*opt); return opt; }
case coEnum: { auto opt = new ConfigOptionEnumGeneric(this->enum_keys_map); archive(*opt); return opt; }
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::load_option_from_archive(): Unknown option type for option ") + this->opt_key);
}
}
}
@ -1623,7 +1690,7 @@ public:
case coInts: archive(*static_cast<const ConfigOptionIntsNullable*>(opt)); break;
case coPercents: archive(*static_cast<const ConfigOptionPercentsNullable*>(opt));break;
case coBools: archive(*static_cast<const ConfigOptionBoolsNullable*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown nullable option type for option ") + this->opt_key);
}
} else {
switch (this->type) {
@ -1642,7 +1709,7 @@ public:
case coBool: archive(*static_cast<const ConfigOptionBool*>(opt)); break;
case coBools: archive(*static_cast<const ConfigOptionBools*>(opt)); break;
case coEnum: archive(*static_cast<const ConfigOptionEnumGeneric*>(opt)); break;
default: throw Slic3r::RuntimeError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
default: throw ConfigurationError(std::string("ConfigOptionDef::save_option_to_archive(): Unknown option type for option ") + this->opt_key);
}
}
// Make the compiler happy, shut up the warnings.
@ -1765,7 +1832,7 @@ public:
return out;
}
/// Iterate through all of the CLI options and write them to a stream.
// Iterate through all of the CLI options and write them to a stream.
std::ostream& print_cli_help(
std::ostream& out, bool show_defaults,
std::function<bool(const ConfigOptionDef &)> filter = [](const ConfigOptionDef &){ return true; }) const;
@ -1934,9 +2001,11 @@ public:
void setenv_() const;
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, ConfigSubstitutionContext& substitutions);
ConfigSubstitutions load_from_ini_string(const std::string &data, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Loading a "will be one day a legacy format" of configuration stored into 3MF or AMF.
// Accepts the same data as load_from_ini_string(), only with each configuration line possibly prefixed with a semicolon (G-code comment).
ConfigSubstitutions load_from_ini_string_commented(std::string &&data, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load_from_gcode_file(const std::string &file, ForwardCompatibilitySubstitutionRule compatibility_rule);
ConfigSubstitutions load(const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
void save(const std::string &file) const;
@ -2099,7 +2168,6 @@ public:
bool opt_bool(const t_config_option_key &opt_key, unsigned int idx) const { return this->option<ConfigOptionBools>(opt_key)->get_at(idx) != 0; }
// Command line processing
void read_cli(const std::vector<std::string> &tokens, t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
bool read_cli(int argc, const char* const argv[], t_config_option_keys* extra, t_config_option_keys* keys = nullptr);
std::map<t_config_option_key, std::unique_ptr<ConfigOption>>::const_iterator cbegin() const { return options.cbegin(); }
@ -2113,9 +2181,9 @@ private:
template<class Archive> void serialize(Archive &ar) { ar(options); }
};
/// Configuration store with a static definition of configuration values.
/// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
/// because the configuration values could be accessed directly.
// Configuration store with a static definition of configuration values.
// In Slic3r, the static configuration stores are during the slicing / g-code generation for efficiency reasons,
// because the configuration values could be accessed directly.
class StaticConfig : public virtual ConfigBase
{
public:

View file

@ -875,7 +875,9 @@ namespace Slic3r {
add_error("Error while reading config data to buffer");
return;
}
config.load_from_gcode_string(buffer.data(), config_substitutions);
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
config_substitutions.substitutions = config.load_from_ini_string_commented(std::move(buffer), config_substitutions.rule);
}
}

View file

@ -706,7 +706,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(), *m_config_substitutions);
//FIXME Loading a "will be one day a legacy format" of configuration in a form of a G-code comment.
// Each config line is prefixed with a semicolon (G-code comment), that is ugly.
m_config_substitutions->substitutions = m_config->load_from_ini_string_commented(std::move(m_value[1].c_str()), m_config_substitutions->rule);
}
else if (strncmp(m_value[0].c_str(), "slic3r.", 7) == 0) {
const char *opt_key = m_value[0].c_str() + 7;

View file

@ -203,7 +203,7 @@ RasterParams get_raster_params(const DynamicPrintConfig &cfg)
if (!opt_disp_cols || !opt_disp_rows || !opt_disp_w || !opt_disp_h ||
!opt_mirror_x || !opt_mirror_y || !opt_orient)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
RasterParams rstp;
@ -229,7 +229,7 @@ SliceParams get_slice_params(const DynamicPrintConfig &cfg)
auto *opt_init_layerh = cfg.option<ConfigOptionFloat>("initial_layer_height");
if (!opt_layerh || !opt_init_layerh)
throw Slic3r::FileIOError("Invalid SL1 file");
throw Slic3r::FileIOError("Invalid SL1 / SL1S file");
return SliceParams{opt_layerh->getFloat(), opt_init_layerh->getFloat()};
}

View file

@ -1464,13 +1464,15 @@ void GCode::_do_export(Print& print, FILE* file, ThumbnailsGeneratorCallback thu
_write_format(file, "; total toolchanges = %i\n", print.m_print_statistics.total_toolchanges);
_write_format(file, ";%s\n", GCodeProcessor::reserved_tag(GCodeProcessor::ETags::Estimated_Printing_Time_Placeholder).c_str());
// Append full config.
_write(file, "\n");
// Append full config, delimited by two 'phony' configuration keys prusaslicer_config = begin and prusaslicer_config = end.
// The delimiters are structured as configuration key / value pairs to be parsable by older versions of PrusaSlicer G-code viewer.
{
_write(file, "\n; prusaslicer_config = begin\n");
std::string full_config;
append_full_config(print, full_config);
if (!full_config.empty())
_write(file, full_config);
_write(file, "; prusaslicer_config = end\n");
}
print.throw_if_canceled();
}
@ -2714,7 +2716,9 @@ std::string GCode::_extrude(const ExtrusionPath &path, std::string description,
// calculate extrusion length per distance unit
double e_per_mm = m_writer.extruder()->e_per_mm3() * path.mm3_per_mm;
if (m_writer.extrusion_axis().empty()) e_per_mm = 0;
if (m_writer.extrusion_axis().empty())
// gcfNoExtrusion
e_per_mm = 0;
// set speed
if (speed == -1) {

View file

@ -1172,7 +1172,7 @@ void GCodeProcessor::process_file(const std::string& filename, bool apply_postpr
// 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);
config.load_from_gcode_file(filename, ForwardCompatibilitySubstitutionRule::EnableSilent);
apply_config(config);
}
else if (m_producer == EProducer::Simplify3D)

View file

@ -337,9 +337,9 @@ namespace Slic3r {
std::string printer;
void reset() {
print = "";
filament = std::vector<std::string>();
printer = "";
print.clear();
filament.clear();
printer.clear();
}
};
std::string filename;

View file

@ -1,10 +1,15 @@
#include "PostProcessor.hpp"
#include "libslic3r/Utils.hpp"
#include "libslic3r/format.hpp"
#include <boost/algorithm/string.hpp>
#include <boost/log/trivial.hpp>
#include <boost/format.hpp>
#include <boost/filesystem.hpp>
#include <boost/nowide/convert.hpp>
#include <boost/nowide/cenv.hpp>
#include <boost/nowide/fstream.hpp>
#ifdef WIN32
@ -179,41 +184,146 @@ static int run_script(const std::string &script, const std::string &gcode, std::
namespace Slic3r {
void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config)
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// For a "File" target, a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config)
{
const auto* post_process = config.opt<ConfigOptionStrings>("post_process");
const auto *post_process = config.opt<ConfigOptionStrings>("post_process");
if (// likely running in SLA mode
post_process == nullptr ||
// no post-processing script
post_process->values.empty())
return;
return false;
std::string path;
if (make_copy) {
// Don't run the post-processing script on the input file, it will be memory mapped by the G-code viewer.
// Make a copy.
path = src_path + ".pp";
// First delete an old file if it exists.
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting an old temporary file %1% before running a post-processing script: %2%", path, err.what());
}
// Second make a copy.
std::string error_message;
if (copy_file(src_path, path, error_message, false) != SUCCESS)
throw Slic3r::RuntimeError(Slic3r::format("Failed making a temporary copy of G-code file %1% before running a post-processing script: %2%", src_path, error_message));
} else {
// Don't make a copy of the G-code before running the post-processing script.
path = src_path;
}
auto delete_copy = [&path, &src_path, make_copy]() {
if (make_copy)
try {
if (boost::filesystem::exists(path))
boost::filesystem::remove(path);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a temporary copy %1% of a G-code file %2% : %3%", path, src_path, err.what());
}
};
// Store print configuration into environment variables.
config.setenv_();
auto gcode_file = boost::filesystem::path(path);
if (! boost::filesystem::exists(gcode_file))
throw Slic3r::RuntimeError(std::string("Post-processor can't find exported gcode file"));
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
// Store print configuration into environment variables.
config.setenv_();
// Let the post-processing script know the target host ("File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...)
boost::nowide::setenv("SLIC3R_PP_HOST", host.c_str(), 1);
// Let the post-processing script know the final file name. For "File" host, it is a full path of the target file name and its location, for example pointing to an SD card.
// For "PrusaLink" or "OctoPrint", it is a file name optionally with a directory on the target host.
boost::nowide::setenv("SLIC3R_PP_OUTPUT_NAME", output_name.c_str(), 1);
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
throw Slic3r::RuntimeError(msg);
// Path to an optional file that the post-processing script may create and populate it with a single line containing the output_name replacement.
std::string path_output_name = path + ".output_name";
auto remove_output_name_file = [&path_output_name, &src_path]() {
try {
if (boost::filesystem::exists(path_output_name))
boost::filesystem::remove(path_output_name);
} catch (const std::exception &err) {
BOOST_LOG_TRIVIAL(error) << Slic3r::format("Failed deleting a file %1% carrying the final name / path of a G-code file %2%: %3%", path_output_name, src_path, err.what());
}
};
// Remove possible stalled path_output_name of the previous run.
remove_output_name_file();
try {
for (const std::string &scripts : post_process->values) {
std::vector<std::string> lines;
boost::split(lines, scripts, boost::is_any_of("\r\n"));
for (std::string script : lines) {
// Ignore empty post processing script lines.
boost::trim(script);
if (script.empty())
continue;
BOOST_LOG_TRIVIAL(info) << "Executing script " << script << " on file " << path;
std::string std_err;
const int result = run_script(script, gcode_file.string(), std_err);
if (result != 0) {
const std::string msg = std_err.empty() ? (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%") % script % path % result).str()
: (boost::format("Post-processing script %1% on file %2% failed.\nError code: %3%\nOutput:\n%4%") % script % path % result % std_err).str();
BOOST_LOG_TRIVIAL(error) << msg;
delete_copy();
throw Slic3r::RuntimeError(msg);
}
}
}
if (boost::filesystem::exists(path_output_name)) {
try {
// Read a single line from path_output_name, which should contain the new output name of the post-processed G-code.
boost::nowide::fstream f;
f.open(path_output_name, std::ios::in);
std::string new_output_name;
std::getline(f, new_output_name);
f.close();
if (host == "File") {
namespace fs = boost::filesystem;
fs::path op(new_output_name);
if (op.is_relative() && op.has_filename() && op.parent_path().empty()) {
// Is this just a filename? Make it an absolute path.
auto outpath = fs::path(output_name).parent_path();
outpath /= op.string();
new_output_name = outpath.string();
}
else {
if (! op.is_absolute() || ! op.has_filename())
throw Slic3r::RuntimeError("Unable to parse desired new path from output name file");
}
if (! fs::exists(fs::path(new_output_name).parent_path()))
throw Slic3r::RuntimeError(Slic3r::format("Output directory does not exist: %1%",
fs::path(new_output_name).parent_path().string()));
}
BOOST_LOG_TRIVIAL(trace) << "Post-processing script changed the file name from " << output_name << " to " << new_output_name;
output_name = new_output_name;
} catch (const std::exception &err) {
throw Slic3r::RuntimeError(Slic3r::format("run_post_process_scripts: Failed reading a file %1% "
"carrying the final name / path of a G-code file: %2%",
path_output_name, err.what()));
}
remove_output_name_file();
}
} catch (...) {
remove_output_name_file();
delete_copy();
throw;
}
src_path = std::move(path);
return true;
}
} // namespace Slic3r

View file

@ -8,7 +8,23 @@
namespace Slic3r {
extern void run_post_process_scripts(const std::string &path, const DynamicPrintConfig &config);
// Run post processing script / scripts if defined.
// Returns true if a post-processing script was executed.
// Returns false if no post-processing script was defined.
// Throws an exception on error.
// host is one of "File", "PrusaLink", "Repetier", "SL1Host", "OctoPrint", "FlashAir", "Duet", "AstroBox" ...
// If make_copy, then a temp file will be created for src_path by adding a ".pp" suffix and src_path will be updated.
// In that case the caller is responsible to delete the temp file created.
// output_name is the final name of the G-code on SD card or when uploaded to PrusaLink or OctoPrint.
// If uploading to PrusaLink or OctoPrint, then the file will be renamed to output_name first on the target host.
// The post-processing script may change the output_name.
extern bool run_post_process_scripts(std::string &src_path, bool make_copy, const std::string &host, std::string &output_name, const DynamicPrintConfig &config);
inline bool run_post_process_scripts(std::string &src_path, const DynamicPrintConfig &config)
{
std::string src_path_name = src_path;
return run_post_process_scripts(src_path, false, "File", src_path_name, config);
}
} // namespace Slic3r

View file

@ -12,16 +12,24 @@
namespace Slic3r {
static inline char get_extrusion_axis_char(const GCodeConfig &config)
{
std::string axis = get_extrusion_axis(config);
assert(axis.size() <= 1);
// Return 0 for gcfNoExtrusion
return axis.empty() ? 0 : axis[0];
}
void GCodeReader::apply_config(const GCodeConfig &config)
{
m_config = config;
m_extrusion_axis = get_extrusion_axis(m_config)[0];
m_extrusion_axis = get_extrusion_axis_char(m_config);
}
void GCodeReader::apply_config(const DynamicPrintConfig &config)
{
m_config.apply(config, true);
m_extrusion_axis = get_extrusion_axis(m_config)[0];
m_extrusion_axis = get_extrusion_axis_char(m_config);
}
const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command)
@ -52,9 +60,10 @@ const char* GCodeReader::parse_line_internal(const char *ptr, GCodeLine &gline,
case 'Z': axis = Z; break;
case 'F': axis = F; break;
default:
if (*c == m_extrusion_axis)
axis = E;
else if (*c >= 'A' && *c <= 'Z')
if (*c == m_extrusion_axis) {
if (m_extrusion_axis != 0)
axis = E;
} else if (*c >= 'A' && *c <= 'Z')
// Unknown axis, but we still want to remember that such a axis was seen.
axis = UNKNOWN_AXIS;
break;
@ -190,6 +199,8 @@ void GCodeReader::GCodeLine::set(const GCodeReader &reader, const Axis axis, con
match[1] = 'F';
else {
assert(axis == E);
// Extruder axis is set.
assert(reader.extrusion_axis() != 0);
match[1] = reader.extrusion_axis();
}

View file

@ -122,8 +122,9 @@ public:
float& f() { return m_position[F]; }
float f() const { return m_position[F]; }
// Returns 0 for gcfNoExtrusion.
char extrusion_axis() const { return m_extrusion_axis; }
void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
// void set_extrusion_axis(char axis) { m_extrusion_axis = axis; }
private:
const char* parse_line_internal(const char *ptr, GCodeLine &gline, std::pair<const char*, const char*> &command);

View file

@ -405,8 +405,10 @@ std::string GCodeWriter::extrude_to_xy(const Vec2d &point, double dE, const std:
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
<< " Y" << XYZF_NUM(point(1));
if (! m_extrusion_axis.empty())
// not gcfNoExtrusion
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
@ -421,8 +423,10 @@ std::string GCodeWriter::extrude_to_xyz(const Vec3d &point, double dE, const std
std::ostringstream gcode;
gcode << "G1 X" << XYZF_NUM(point(0))
<< " Y" << XYZF_NUM(point(1))
<< " Z" << XYZF_NUM(point(2))
<< " " << m_extrusion_axis << E_NUM(m_extruder->E());
<< " Z" << XYZF_NUM(point(2));
if (! m_extrusion_axis.empty())
// not gcfNoExtrusion
gcode << " " << m_extrusion_axis << E_NUM(m_extruder->E());
COMMENT(comment);
gcode << "\n";
return gcode.str();
@ -474,7 +478,7 @@ std::string GCodeWriter::_retract(double length, double restart_extra, const std
gcode << "G22 ; retract\n";
else
gcode << "G10 ; retract\n";
} else {
} else if (! m_extrusion_axis.empty()) {
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->retract_speed() * 60.);
COMMENT(comment);
@ -503,7 +507,7 @@ std::string GCodeWriter::unretract()
else
gcode << "G11 ; unretract\n";
gcode << this->reset_e();
} else {
} else if (! m_extrusion_axis.empty()) {
// use G1 instead of G0 because G0 will blend the restart with the previous travel move
gcode << "G1 " << m_extrusion_axis << E_NUM(m_extruder->E())
<< " F" << XYZF_NUM(m_extruder->deretract_speed() * 60.);

View file

@ -25,6 +25,7 @@ public:
Extruder* extruder() { return m_extruder; }
const Extruder* extruder() const { return m_extruder; }
// Returns empty string for gcfNoExtrusion.
std::string extrusion_axis() const { return m_extrusion_axis; }
void apply_print_config(const PrintConfig &print_config);
// Extruders are expected to be sorted in an increasing order.

View file

@ -157,6 +157,7 @@ template<class Its> bool its_is_splittable(const Its &m)
const auto& neighbor_index = ItsWithNeighborsIndex_<Its>::get_index(m);
std::vector<char> visited(its.indices.size(), false);
its_find_unvisited_neighbors(its, neighbor_index, visited);
auto faces = its_find_unvisited_neighbors(its, neighbor_index, visited);
return !faces.empty();

View file

@ -1240,221 +1240,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
const size_t num_layers = input_expolygons.size();
const ConstLayerPtrsAdaptor layers = print_object.layers();
#if 0
auto get_extrusion_width = [&layers = std::as_const(layers)](const size_t layer_idx) -> float {
auto extrusion_width_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().perimeter_extrusion_width <
l2->region().config().perimeter_extrusion_width;
});
assert(extrusion_width_it != layers[layer_idx]->regions().end());
return float((*extrusion_width_it)->region().config().perimeter_extrusion_width);
};
auto get_top_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
auto top_solid_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().top_solid_layers < l2->region().config().top_solid_layers;
});
assert(top_solid_layer_it != layers[layer_idx]->regions().end());
return (*top_solid_layer_it)->region().config().top_solid_layers;
};
auto get_bottom_solid_layers = [&layers = std::as_const(layers)](const size_t layer_idx) -> int {
auto top_bottom_layer_it = std::max_element(layers[layer_idx]->regions().begin(), layers[layer_idx]->regions().end(),
[](const LayerRegion *l1, const LayerRegion *l2) {
return l1->region().config().bottom_solid_layers < l2->region().config().bottom_solid_layers;
});
assert(top_bottom_layer_it != layers[layer_idx]->regions().end());
return (*top_bottom_layer_it)->region().config().bottom_solid_layers;
};
std::vector<ExPolygons> top_layers(num_layers);
top_layers.back() = input_expolygons.back();
tbb::parallel_for(tbb::blocked_range<size_t>(1, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
top_layers[layer_idx - 1] = diff_ex(input_expolygons[layer_idx - 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
}
}); // end of parallel_for
std::vector<ExPolygons> bottom_layers(num_layers);
bottom_layers.front() = input_expolygons.front();
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers - 1), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float extrusion_width = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
bottom_layers[layer_idx + 1] = diff_ex(input_expolygons[layer_idx + 1], offset_ex(input_expolygons[layer_idx], extrusion_width));
}
}); // end of parallel_for
std::vector<std::vector<ClipperLib::Paths>> triangles_by_color_raw(num_extruders, std::vector<ClipperLib::Paths>(layers.size()));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - begin";
{
auto delta = float(10 * SCALED_EPSILON);
std::vector<float> deltas { delta, delta, delta };
Points projected_facet;
for (const ModelVolume *mv : print_object.model_object()->volumes)
if (mv->is_model_part()) {
const Transform3f tr = print_object.trafo().cast<float>() * mv->get_matrix().cast<float>();
for (size_t extruder_idx = 0; extruder_idx < num_extruders; ++extruder_idx) {
const indexed_triangle_set custom_facets = mv->mmu_segmentation_facets.get_facets(*mv, EnforcerBlockerType(extruder_idx));
if (custom_facets.indices.empty())
continue;
throw_on_cancel_callback();
for (size_t facet_idx = 0; facet_idx < custom_facets.indices.size(); ++facet_idx) {
float min_z = std::numeric_limits<float>::max();
float max_z = std::numeric_limits<float>::lowest();
std::array<Vec3f, 3> facet;
for (int p_idx = 0; p_idx < 3; ++p_idx) {
facet[p_idx] = tr * custom_facets.vertices[custom_facets.indices[facet_idx](p_idx)];
max_z = std::max(max_z, facet[p_idx].z());
min_z = std::min(min_z, facet[p_idx].z());
}
// Sort the vertices by z-axis for simplification of projected_facet on slices
std::sort(facet.begin(), facet.end(), [](const Vec3f &p1, const Vec3f &p2) { return p1.z() < p2.z(); });
projected_facet.clear();
projected_facet.reserve(3);
for (int p_idx = 0; p_idx < 3; ++p_idx)
projected_facet.emplace_back(Point(scale_(facet[p_idx].x()), scale_(facet[p_idx].y())) - print_object.center_offset());
if (cross2((projected_facet[1] - projected_facet[0]).cast<int64_t>(), (projected_facet[2] - projected_facet[1]).cast<int64_t>()) < 0)
// Make CCW.
std::swap(projected_facet[1], projected_facet[2]);
ClipperLib::Path offsetted = mittered_offset_path_scaled(projected_facet, deltas, 3.);
// Find lowest slice not below the triangle.
auto first_layer = std::upper_bound(layers.begin(), layers.end(), float(min_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
auto last_layer = std::upper_bound(layers.begin(), layers.end(), float(max_z - EPSILON),
[](float z, const Layer *l1) { return z < l1->slice_z + l1->height * 0.5; });
if (last_layer == layers.end())
--last_layer;
if (first_layer == layers.end() || (first_layer != layers.begin() && facet[0].z() < (*first_layer)->print_z - EPSILON))
--first_layer;
for (auto layer_it = first_layer; (layer_it != (last_layer + 1) && layer_it != layers.end()); ++layer_it)
if (size_t layer_idx = layer_it - layers.begin(); ! top_layers[layer_idx].empty() || ! bottom_layers[layer_idx].empty())
triangles_by_color_raw[extruder_idx][layer_idx].emplace_back(offsetted);
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - top and bottom layers - projection of painted triangles - end";
std::vector<std::vector<ExPolygons>> triangles_by_color(num_extruders, std::vector<ExPolygons>(layers.size()));
tbb::parallel_for(tbb::blocked_range<size_t>(0, num_layers), [&](const tbb::blocked_range<size_t> &range) {
for (size_t layer_idx = range.begin(); layer_idx < range.end(); ++layer_idx) {
throw_on_cancel_callback();
float offset_factor = 0.1f * float(scale_(get_extrusion_width(layer_idx)));
for (size_t extruder_id = 0; extruder_id < num_extruders; ++ extruder_id)
if (ClipperLib::Paths &src_paths = triangles_by_color_raw[extruder_id][layer_idx]; !src_paths.empty())
triangles_by_color[extruder_id][layer_idx] = offset_ex(offset_ex(ClipperPaths_to_Slic3rExPolygons(src_paths), -offset_factor), offset_factor);
}
}); // end of parallel_for
triangles_by_color_raw.clear();
std::vector<std::vector<ExPolygons>> triangles_by_color_bottom(num_extruders);
std::vector<std::vector<ExPolygons>> triangles_by_color_top(num_extruders);
triangles_by_color_bottom.assign(num_extruders, std::vector<ExPolygons>(num_layers));
triangles_by_color_top.assign(num_extruders, std::vector<ExPolygons>(num_layers));
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - begin";
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int top_solid_layers = get_top_solid_layers(layer_idx);
ExPolygons top_expolygon = top_layers[layer_idx];
if (top_expolygon.empty())
continue;
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
throw_on_cancel_callback();
if (triangles_by_color[color_idx][layer_idx].empty())
continue;
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], top_expolygon);
if (!intersection_poly.empty()) {
triangles_by_color_top[color_idx][layer_idx].insert(triangles_by_color_top[color_idx][layer_idx].end(), intersection_poly.begin(),
intersection_poly.end());
for (int last_idx = int(layer_idx) - 1; last_idx >= std::max(int(layer_idx - top_solid_layers), int(0)); --last_idx) {
float offset_value = float(layer_idx - last_idx) * (-1.0f) * extrusion_width;
if (offset_ex(top_expolygon, offset_value).empty())
continue;
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
for (int last_idx_1 = last_idx; last_idx_1 < int(layer_idx); ++last_idx_1) {
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, input_expolygons[last_idx_1 + 1]);
}
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_top[color_idx][layer_idx], offset_e);
triangles_by_color_top[color_idx][last_idx].insert(triangles_by_color_top[color_idx][last_idx].end(), intersection_poly_2.begin(),
intersection_poly_2.end());
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of top layer - end";
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - begin";
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
float extrusion_width = scale_(get_extrusion_width(layer_idx));
int bottom_solid_layers = get_bottom_solid_layers(layer_idx);
const ExPolygons &bottom_expolygon = bottom_layers[layer_idx];
if (bottom_expolygon.empty())
continue;
for (size_t color_idx = 0; color_idx < triangles_by_color.size(); ++color_idx) {
throw_on_cancel_callback();
if (triangles_by_color[color_idx][layer_idx].empty())
continue;
ExPolygons intersection_poly = intersection_ex(triangles_by_color[color_idx][layer_idx], bottom_expolygon);
if (!intersection_poly.empty()) {
triangles_by_color_bottom[color_idx][layer_idx].insert(triangles_by_color_bottom[color_idx][layer_idx].end(), intersection_poly.begin(),
intersection_poly.end());
for (size_t last_idx = layer_idx + 1; last_idx < std::min(layer_idx + bottom_solid_layers, num_layers); ++last_idx) {
float offset_value = float(last_idx - layer_idx) * (-1.0f) * extrusion_width;
if (offset_ex(bottom_expolygon, offset_value).empty())
continue;
ExPolygons layer_slices_trimmed = input_expolygons[last_idx];
for (int last_idx_1 = int(last_idx); last_idx_1 > int(layer_idx); --last_idx_1) {
layer_slices_trimmed = intersection_ex(layer_slices_trimmed, offset_ex(input_expolygons[last_idx_1 - 1], offset_value));
}
ExPolygons offset_e = offset_ex(layer_slices_trimmed, offset_value);
ExPolygons intersection_poly_2 = intersection_ex(triangles_by_color_bottom[color_idx][layer_idx], offset_e);
append(triangles_by_color_bottom[color_idx][last_idx], std::move(intersection_poly_2));
}
}
}
}
BOOST_LOG_TRIVIAL(debug) << "MMU segmentation - segmentation of bottom layer - end";
std::vector<std::vector<ExPolygons>> triangles_by_color_merged(num_extruders);
triangles_by_color_merged.assign(num_extruders, std::vector<ExPolygons>(num_layers));
for (size_t layer_idx = 0; layer_idx < num_layers; ++layer_idx) {
throw_on_cancel_callback();
for (size_t color_idx = 0; color_idx < triangles_by_color_merged.size(); ++color_idx) {
auto &self = triangles_by_color_merged[color_idx][layer_idx];
append(self, std::move(triangles_by_color_bottom[color_idx][layer_idx]));
append(self, std::move(triangles_by_color_top[color_idx][layer_idx]));
self = union_ex(self);
}
// Cut all colors for cases when two colors are overlapping
for (size_t color_idx = 1; color_idx < triangles_by_color_merged.size(); ++color_idx) {
triangles_by_color_merged[color_idx][layer_idx] = diff_ex(triangles_by_color_merged[color_idx][layer_idx],
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
}
#else
// Maximum number of top / bottom layers accounts for maximum overlap of one thread group into a neighbor thread group.
int max_top_layers = 0;
int max_bottom_layers = 0;
@ -1470,8 +1255,7 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
// project downards pointing painted triangles over bottom surfaces.
std::vector<std::vector<Polygons>> top_raw(num_extruders), bottom_raw(num_extruders);
std::vector<float> zs = zs_from_layers(print_object.layers());
Transform3d object_trafo = print_object.trafo();
object_trafo.pretranslate(Vec3d(- unscale<double>(print_object.center_offset().x()), - unscale<double>(print_object.center_offset().y()), 0));
Transform3d object_trafo = print_object.trafo_centered();
#ifdef MMU_SEGMENTATION_DEBUG_TOP_BOTTOM
static int iRun = 0;
@ -1650,7 +1434,6 @@ static inline std::vector<std::vector<ExPolygons>> mmu_segmentation_top_and_bott
triangles_by_color_merged[color_idx - 1][layer_idx]);
}
});
#endif
return triangles_by_color_merged;
}

View file

@ -191,7 +191,9 @@ void PresetBundle::setup_directories()
PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, ForwardCompatibilitySubstitutionRule substitution_rule, const std::string &preferred_model_id)
{
// First load the vendor specific system presets.
std::string errors_cummulative = this->load_system_presets();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
std::tie(substitutions, errors_cummulative) = this->load_system_presets(substitution_rule);
const std::string dir_user_presets = data_dir()
#ifdef SLIC3R_PROFILE_USE_PRESETS_SUBDIR
@ -202,7 +204,6 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
#endif
;
PresetsConfigSubstitutions substitutions;
try {
this->prints.load_presets(dir_user_presets, "print", substitutions, substitution_rule);
} catch (const std::runtime_error &err) {
@ -245,12 +246,20 @@ PresetsConfigSubstitutions PresetBundle::load_presets(AppConfig &config, Forward
// Load system presets into this PresetBundle.
// For each vendor, there will be a single PresetBundle loaded.
std::string PresetBundle::load_system_presets()
std::pair<PresetsConfigSubstitutions, std::string> PresetBundle::load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule)
{
if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSystemSilent)
// Loading system presets, don't log substitutions.
compatibility_rule = ForwardCompatibilitySubstitutionRule::EnableSilent;
else if (compatibility_rule == ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem)
// Loading system presets, throw on unknown option value.
compatibility_rule = ForwardCompatibilitySubstitutionRule::Disable;
// Here the vendor specific read only Config Bundles are stored.
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
std::string errors_cummulative;
bool first = true;
boost::filesystem::path dir = (boost::filesystem::path(data_dir()) / "vendor").make_preferred();
PresetsConfigSubstitutions substitutions;
std::string errors_cummulative;
bool first = true;
for (auto &dir_entry : boost::filesystem::directory_iterator(dir))
if (Slic3r::is_ini_file(dir_entry)) {
std::string name = dir_entry.path().filename().string();
@ -260,13 +269,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(), PresetBundle::LoadSystem);
append(substitutions, this->load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
first = false;
} else {
// Load the other vendor configs, merge them with this PresetBundle.
// Report duplicate profiles.
PresetBundle other;
other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem);
append(substitutions, other.load_configbundle(dir_entry.path().string(), PresetBundle::LoadSystem, compatibility_rule).first);
std::vector<std::string> duplicates = this->merge_presets(std::move(other));
if (! duplicates.empty()) {
errors_cummulative += "Vendor configuration file " + name + " contains the following presets with names used by other vendors: ";
@ -288,7 +297,7 @@ std::string PresetBundle::load_system_presets()
}
this->update_system_maps();
return errors_cummulative;
return std::make_pair(std::move(substitutions), errors_cummulative);
}
// Merge one vendor's presets with the other vendor's presets, report duplicates.
@ -700,7 +709,7 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
if (is_gcode_file(path)) {
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, true /* check_header */, compatibility_rule);
ConfigSubstitutions config_substitutions = config.load_from_gcode_file(path, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
@ -714,8 +723,8 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
} catch (const std::ifstream::failure &err) {
throw Slic3r::RuntimeError(std::string("The Config Bundle cannot be loaded: ") + path + "\n\tReason: " + err.what());
} catch (const boost::property_tree::file_parser_error &err) {
throw Slic3r::RuntimeError((boost::format("Failed loading the Config Bundle \"%1%\": %2% at line %3%")
% err.filename() % err.message() % err.line()).str());
throw Slic3r::RuntimeError(format("Failed loading the Config Bundle \"%1%\": %2% at line %3%",
err.filename(), err.message(), err.line()));
} catch (const std::runtime_error &err) {
throw Slic3r::RuntimeError(std::string("Failed loading the preset file: ") + path + "\n\tReason: " + err.what());
}
@ -723,23 +732,27 @@ ConfigSubstitutions PresetBundle::load_config_file(const std::string &path, Forw
// 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);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree);
try {
switch (config_file_type) {
case CONFIG_FILE_TYPE_UNKNOWN:
throw Slic3r::RuntimeError(std::string("Unknown configuration file type: ") + path);
case CONFIG_FILE_TYPE_APP_CONFIG:
throw Slic3r::RuntimeError(std::string("Invalid configuration file: ") + path + ". This is an application config file.");
case CONFIG_FILE_TYPE_CONFIG:
{
// Initialize a config from full defaults.
DynamicPrintConfig config;
config.apply(FullPrintConfig::defaults());
config_substitutions = config.load(tree, compatibility_rule);
Preset::normalize(config);
load_config_file_config(path, true, std::move(config));
return config_substitutions;
}
case CONFIG_FILE_TYPE_CONFIG_BUNDLE:
return load_config_file_config_bundle(path, tree, compatibility_rule);
}
} catch (const ConfigurationError &e) {
throw Slic3r::RuntimeError(format("Invalid configuration file %1%: %2%", path, e.what()));
}
// This shall never happen. Suppres compiler warnings.
@ -916,13 +929,14 @@ 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.
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree)
ConfigSubstitutions PresetBundle::load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// 1) Load the config bundle into a temp data.
PresetBundle tmp_bundle;
// Load the config bundle, 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, {});
auto [presets_substitutions, presets_imported] = tmp_bundle.load_configbundle(path, {}, compatibility_rule);
UNUSED(presets_imported);
std::string bundle_name = std::string(" - ") + boost::filesystem::path(path).filename().string();
@ -1135,15 +1149,11 @@ 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.
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(const std::string &path, LoadConfigBundleAttributes flags)
std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule)
{
// 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
};
ConfigSubstitutionContext substitution_context { compatibility_rule };
PresetsConfigSubstitutions substitutions;
if (flags.has(LoadConfigBundleAttribute::ResetUserProfile) || flags.has(LoadConfigBundleAttribute::LoadSystem))
@ -1238,7 +1248,7 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
active_sla_material = kvp.second.data();
} else if (kvp.first == "printer") {
active_printer = kvp.second.data();
}else if (kvp.first == "physical_printer") {
} else if (kvp.first == "physical_printer") {
active_physical_printer = kvp.second.data();
}
}
@ -1275,32 +1285,36 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
DynamicPrintConfig config;
std::string alias_name;
std::vector<std::string> renamed_from;
auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
try {
auto parse_config_section = [&section, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
substitution_context.substitutions.clear();
for (auto &kvp : section.second) {
if (kvp.first == "alias")
alias_name = kvp.second.data();
else if (kvp.first == "renamed_from") {
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
}
}
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
}
};
if (presets == &this->printers) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
}
};
if (presets == &this->printers) {
// Select the default config based on the printer_technology field extracted from kvp.
DynamicPrintConfig config_src;
parse_config_section(config_src);
default_config = &presets->default_preset_for(config_src).config;
config = *default_config;
config.apply(config_src);
} else {
default_config = &presets->default_preset().config;
config = *default_config;
parse_config_section(config);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
Preset::normalize(config);
// Report configuration fields, which are misplaced into a wrong group.
@ -1408,8 +1422,12 @@ std::pair<PresetsConfigSubstitutions, size_t> PresetBundle::load_configbundle(co
DynamicPrintConfig config = default_config;
substitution_context.substitutions.clear();
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
try {
for (auto& kvp : section.second)
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
} catch (const ConfigurationError &e) {
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
}
// Report configuration fields, which are misplaced into a wrong group.
std::string incorrect_keys = Preset::remove_invalid_keys(config, default_config);

View file

@ -102,7 +102,8 @@ public:
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);
std::pair<PresetsConfigSubstitutions, size_t> load_configbundle(
const std::string &path, LoadConfigBundleAttributes flags, ForwardCompatibilitySubstitutionRule compatibility_rule);
// Export a config bundle file containing all the presets and the names of the active presets.
void export_configbundle(const std::string &path, bool export_system_settings = false, bool export_physical_printers = false);
@ -139,7 +140,7 @@ public:
static const char *PRUSA_BUNDLE;
private:
std::string load_system_presets();
std::pair<PresetsConfigSubstitutions, std::string> load_system_presets(ForwardCompatibilitySubstitutionRule compatibility_rule);
// Merge one vendor's presets with the other vendor's presets, report duplicates.
std::vector<std::string> merge_presets(PresetBundle &&other);
// Update renamed_from and alias maps of system profiles.
@ -158,7 +159,8 @@ 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);
ConfigSubstitutions load_config_file_config_bundle(const std::string &path, const boost::property_tree::ptree &tree);
ConfigSubstitutions load_config_file_config_bundle(
const std::string &path, const boost::property_tree::ptree &tree, ForwardCompatibilitySubstitutionRule compatibility_rule);
DynamicPrintConfig full_fff_config() const;
DynamicPrintConfig full_sla_config() const;

View file

@ -253,6 +253,9 @@ public:
ConstLayerPtrsAdaptor layers() const { return ConstLayerPtrsAdaptor(&m_layers); }
ConstSupportLayerPtrsAdaptor support_layers() const { return ConstSupportLayerPtrsAdaptor(&m_support_layers); }
const Transform3d& trafo() const { return m_trafo; }
// Trafo with the center_offset() applied after the transformation, to center the object in XY before slicing.
Transform3d trafo_centered() const
{ Transform3d t = this->trafo(); t.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0)); return t; }
const PrintInstances& instances() const { return m_instances; }
// Whoever will get a non-const pointer to PrintObject will be able to modify its layers.

View file

@ -234,7 +234,7 @@ void PrintConfigDef::init_common_params()
def = this->add("thumbnails", coPoints);
def->label = L("G-code thumbnails");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 files, in the following format: \"XxY, XxY, ...\"");
def->tooltip = L("Picture sizes to be stored into a .gcode and .sl1 / .sl1s files, in the following format: \"XxY, XxY, ...\"");
def->mode = comExpert;
def->gui_type = ConfigOptionDef::GUIType::one_string;
def->set_default_value(new ConfigOptionPoints());
@ -493,7 +493,7 @@ void PrintConfigDef::init_fff_params()
def->category = L("Skirt and brim");
def->tooltip = L("The offset of the brim from the printed object.");
def->sidetext = L("mm");
def->mode = comSimple;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(0));
def = this->add("clip_multipart_objects", coBool);

View file

@ -428,10 +428,8 @@ std::pair<FillAdaptive::OctreePtr, FillAdaptive::OctreePtr> PrintObject::prepare
indexed_triangle_set mesh = this->model_object()->raw_indexed_triangle_set();
// Rotate mesh and build octree on it with axis-aligned (standart base) cubes.
Transform3d m = m_trafo;
m.pretranslate(Vec3d(- unscale<float>(m_center_offset.x()), - unscale<float>(m_center_offset.y()), 0));
auto to_octree = transform_to_octree().toRotationMatrix();
its_transform(mesh, to_octree * m, true);
its_transform(mesh, to_octree * this->trafo_centered(), true);
// Triangulate internal bridging surfaces.
std::vector<std::vector<Vec3d>> overhangs(this->layers().size());
@ -2298,7 +2296,7 @@ void PrintObject::project_and_append_custom_facets(
: mv->supported_facets.get_facets_strict(*mv, type);
if (! custom_facets.indices.empty())
project_triangles_to_slabs(this->layers(), custom_facets,
(Eigen::Translation3d(to_3d(unscaled<double>(this->center_offset()), 0.)) * this->trafo() * mv->get_matrix()).cast<float>(),
(this->trafo_centered() * mv->get_matrix()).cast<float>(),
seam, out);
}
}

View file

@ -695,11 +695,9 @@ void PrintObject::slice_volumes()
}
std::vector<float> slice_zs = zs_from_layers(m_layers);
Transform3d trafo = this->trafo();
trafo.pretranslate(Vec3d(- unscale<double>(m_center_offset.x()), - unscale<double>(m_center_offset.y()), 0));
std::vector<std::vector<ExPolygons>> region_slices = slices_to_regions(this->model_object()->volumes, *m_shared_regions, slice_zs,
slice_volumes_inner(
print->config(), this->config(), trafo,
print->config(), this->config(), this->trafo_centered(),
this->model_object()->volumes, m_shared_regions->layer_ranges, slice_zs, throw_on_cancel_callback),
m_config.clip_multipart_objects,
throw_on_cancel_callback);
@ -832,8 +830,7 @@ std::vector<Polygons> PrintObject::slice_support_volumes(const ModelVolumeType m
const Print *print = this->print();
auto throw_on_cancel_callback = std::function<void()>([print](){ print->throw_if_canceled(); });
MeshSlicingParamsEx params;
params.trafo = this->trafo();
params.trafo.pretranslate(Vec3d(-unscale<float>(m_center_offset.x()), -unscale<float>(m_center_offset.y()), 0));
params.trafo = this->trafo_centered();
for (; it_volume != it_volume_end; ++ it_volume)
if ((*it_volume)->type() == model_volume_type) {
std::vector<ExPolygons> slices2 = slice_volume(*(*it_volume), zs, params, throw_on_cancel_callback);

View file

@ -1,5 +1,6 @@
#include "QuadricEdgeCollapse.hpp"
#include <tuple>
#include <optional>
#include "MutablePriorityQueue.hpp"
#include "SimplifyMeshImpl.hpp"
#include <tbb/parallel_for.h>

View file

@ -25,8 +25,17 @@ public:
Semver() : ver(semver_zero()) {}
Semver(int major, int minor, int patch,
boost::optional<const std::string&> metadata = boost::none,
boost::optional<const std::string&> prerelease = boost::none)
boost::optional<const std::string&> metadata, boost::optional<const std::string&> prerelease)
: ver(semver_zero())
{
ver.major = major;
ver.minor = minor;
ver.patch = patch;
set_metadata(metadata);
set_prerelease(prerelease);
}
Semver(int major, int minor, int patch, const char *metadata = nullptr, const char *prerelease = nullptr)
: ver(semver_zero())
{
ver.major = major;
@ -102,7 +111,9 @@ public:
void set_min(int min) { ver.minor = min; }
void set_patch(int patch) { ver.patch = patch; }
void set_metadata(boost::optional<const std::string&> meta) { ver.metadata = meta ? strdup(*meta) : nullptr; }
void set_metadata(const char *meta) { ver.metadata = meta ? strdup(meta) : nullptr; }
void set_prerelease(boost::optional<const std::string&> pre) { ver.prerelease = pre ? strdup(*pre) : nullptr; }
void set_prerelease(const char *pre) { ver.prerelease = pre ? strdup(pre) : nullptr; }
// Comparison
bool operator<(const Semver &b) const { return ::semver_compare(ver, b.ver) == -1; }

View file

@ -178,12 +178,12 @@ void TriangleSelector::select_patch(const Vec3f& hit, int facet_start,
}
}
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle)
void TriangleSelector::seed_fill_select_triangles(const Vec3f &hit, int facet_start, float seed_fill_angle, bool force_reselection)
{
assert(facet_start < m_orig_size_indices);
// Recompute seed fill only if the cursor is pointing on facet unselected by seed fill.
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill())
if (int start_facet_idx = select_unsplit_triangle(hit, facet_start); start_facet_idx >= 0 && m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection)
return;
this->seed_fill_unselect_all_triangles();
@ -278,7 +278,7 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
return;
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_subtriangles_out](const int subtriangle_idx, Partition partition) -> void {
assert(subtriangle_idx == -1);
assert(subtriangle_idx != -1);
if (!m_triangles[subtriangle_idx].is_split())
touching_subtriangles_out.emplace_back(subtriangle_idx);
else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
@ -295,11 +295,48 @@ void TriangleSelector::append_touching_subtriangles(int itriangle, int vertexi,
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate)
// It appends all edges that are touching the edge (vertexi, vertexj) of the triangle and are not selected by seed fill
// It doesn't append the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
void TriangleSelector::append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const
{
if (itriangle == -1)
return;
auto process_subtriangle = [this, &itriangle, &vertexi, &vertexj, &touching_edges_out](const int subtriangle_idx, Partition partition) -> void {
assert(subtriangle_idx != -1);
if (!m_triangles[subtriangle_idx].is_split()) {
if (!m_triangles[subtriangle_idx].is_selected_by_seed_fill()) {
int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj);
if (partition == Partition::First && midpoint != -1) {
touching_edges_out.emplace_back(vertexi, midpoint);
} else if (partition == Partition::First && midpoint == -1) {
touching_edges_out.emplace_back(vertexi, vertexj);
} else {
assert(midpoint != -1 && partition == Partition::Second);
touching_edges_out.emplace_back(midpoint, vertexj);
}
}
} else if (int midpoint = this->triangle_midpoint(itriangle, vertexi, vertexj); midpoint != -1)
append_touching_edges(subtriangle_idx, partition == Partition::First ? vertexi : midpoint, partition == Partition::First ? midpoint : vertexj,
touching_edges_out);
else
append_touching_edges(subtriangle_idx, vertexi, vertexj, touching_edges_out);
};
std::pair<int, int> touching = this->triangle_subtriangles(itriangle, vertexi, vertexj);
if (touching.first != -1)
process_subtriangle(touching.first, Partition::First);
if (touching.second != -1)
process_subtriangle(touching.second, Partition::Second);
}
void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_start, bool propagate, bool force_reselection)
{
int start_facet_idx = select_unsplit_triangle(hit, facet_start);
assert(start_facet_idx != -1);
// Recompute bucket fill only if the cursor is pointing on facet unselected by bucket fill.
if (start_facet_idx == -1 || m_triangles[start_facet_idx].is_selected_by_seed_fill())
if (start_facet_idx == -1 || (m_triangles[start_facet_idx].is_selected_by_seed_fill() && !force_reselection))
return;
assert(!m_triangles[start_facet_idx].is_split());
@ -312,7 +349,7 @@ void TriangleSelector::bucket_fill_select_triangles(const Vec3f& hit, int facet_
}
auto get_all_touching_triangles = [this](int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated) -> std::vector<int> {
assert(facet_idx != -1 && facet_idx < m_triangles.size());
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
std::vector<int> touching_triangles;
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
@ -1358,6 +1395,48 @@ void TriangleSelector::get_facets_split_by_tjoints(const Vec3i &vertices, const
}
}
std::vector<Vec2i> TriangleSelector::get_seed_fill_contour() const {
std::vector<Vec2i> edges_out;
for (int facet_idx = 0; facet_idx < this->m_orig_size_indices; ++facet_idx) {
const Vec3i neighbors = root_neighbors(*m_mesh, facet_idx);
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
this->get_seed_fill_contour_recursive(facet_idx, neighbors, neighbors, edges_out);
}
return edges_out;
}
void TriangleSelector::get_seed_fill_contour_recursive(const int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const {
assert(facet_idx != -1 && facet_idx < int(m_triangles.size()));
assert(this->verify_triangle_neighbors(m_triangles[facet_idx], neighbors));
const Triangle *tr = &m_triangles[facet_idx];
if (!tr->valid())
return;
if (tr->is_split()) {
int num_of_children = tr->number_of_split_sides() + 1;
if (num_of_children != 1) {
for (int i = 0; i < num_of_children; ++i) {
assert(i < int(tr->children.size()));
assert(tr->children[i] < int(m_triangles.size()));
// Recursion, deep first search over the children of this triangle.
// All children of this triangle were created by splitting a single source triangle of the original mesh.
this->get_seed_fill_contour_recursive(tr->children[i], this->child_neighbors(*tr, neighbors, i), this->child_neighbors_propagated(*tr, neighbors_propagated, i), edges_out);
}
}
} else if (tr->is_selected_by_seed_fill()) {
Vec3i vertices = {m_triangles[facet_idx].verts_idxs[0], m_triangles[facet_idx].verts_idxs[1], m_triangles[facet_idx].verts_idxs[2]};
append_touching_edges(neighbors(0), vertices(1), vertices(0), edges_out);
append_touching_edges(neighbors(1), vertices(2), vertices(1), edges_out);
append_touching_edges(neighbors(2), vertices(0), vertices(2), edges_out);
// It appends the edges that are touching the triangle only by part of the edge that means the triangles are from lower depth.
for (int idx = 0; idx < 3; ++idx)
if (int neighbor_tr_idx = neighbors_propagated(idx); neighbor_tr_idx != -1 && !m_triangles[neighbor_tr_idx].is_split() && !m_triangles[neighbor_tr_idx].is_selected_by_seed_fill())
edges_out.emplace_back(vertices(idx), vertices(next_idx_modulo(idx, 3)));
}
}
std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> TriangleSelector::serialize() const
{
// Each original triangle of the mesh is assigned a number encoding its state

View file

@ -49,11 +49,13 @@ public:
void seed_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
float seed_fill_angle); // the maximal angle between two facets to be painted by the same color
float seed_fill_angle, // the maximal angle between two facets to be painted by the same color
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
void bucket_fill_select_triangles(const Vec3f &hit, // point where to start
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate); // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
int facet_start, // facet of the original mesh (unsplit) that the hit point belongs to
bool propagate, // if bucket fill is propagated to neighbor faces or if it fills the only facet of the modified mesh that the hit point belongs to.
bool force_reselection = false); // force reselection of the triangle mesh even in cases that mouse is pointing on the selected triangle
bool has_facets(EnforcerBlockerType state) const;
static bool has_facets(const std::pair<std::vector<std::pair<int, int>>, std::vector<bool>> &data, EnforcerBlockerType test_state);
@ -62,6 +64,8 @@ public:
indexed_triangle_set get_facets(EnforcerBlockerType state) const;
// Get facets at a given state. Triangulate T-joints.
indexed_triangle_set get_facets_strict(EnforcerBlockerType state) const;
// Get edges around the selected area by seed fill.
std::vector<Vec2i> get_seed_fill_contour() const;
// Set facet of the mesh to a given state. Only works for original triangles.
void set_facet(int facet_idx, EnforcerBlockerType state);
@ -221,6 +225,7 @@ private:
std::pair<int, int> triangle_subtriangles(int itriangle, int vertexi, int vertexj) const;
void append_touching_subtriangles(int itriangle, int vertexi, int vertexj, std::vector<int> &touching_subtriangles_out) const;
void append_touching_edges(int itriangle, int vertexi, int vertexj, std::vector<Vec2i> &touching_edges_out) const;
#ifndef NDEBUG
bool verify_triangle_neighbors(const Triangle& tr, const Vec3i& neighbors) const;
@ -234,6 +239,8 @@ private:
std::vector<stl_triangle_vertex_indices> &out_triangles) const;
void get_facets_split_by_tjoints(const Vec3i &vertices, const Vec3i &neighbors, std::vector<stl_triangle_vertex_indices> &out_triangles) const;
void get_seed_fill_contour_recursive(int facet_idx, const Vec3i &neighbors, const Vec3i &neighbors_propagated, std::vector<Vec2i> &edges_out) const;
int m_free_triangles_head { -1 };
int m_free_vertices_head { -1 };
};

View file

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

View file

@ -888,7 +888,7 @@ unsigned get_current_pid()
#endif
}
std::string xml_escape(std::string text)
std::string xml_escape(std::string text, bool is_marked/* = false*/)
{
std::string::size_type pos = 0;
for (;;)
@ -903,8 +903,8 @@ std::string xml_escape(std::string text)
case '\"': replacement = "&quot;"; break;
case '\'': replacement = "&apos;"; break;
case '&': replacement = "&amp;"; break;
case '<': replacement = "&lt;"; break;
case '>': replacement = "&gt;"; break;
case '<': replacement = is_marked ? "<" :"&lt;"; break;
case '>': replacement = is_marked ? ">" :"&gt;"; break;
default: break;
}