mirror of
https://github.com/SoftFever/OrcaSlicer.git
synced 2025-10-22 16:21:24 -06:00
Merge branch 'master' into fs_QuadricEdgeCollapse
This commit is contained in:
commit
b90ca142a5
116 changed files with 3328 additions and 1335 deletions
|
@ -124,32 +124,34 @@ namespace ImGui
|
|||
const char ColorMarkerEnd = 0x3; // ETX
|
||||
|
||||
// Special ASCII characters are used here as an ikons markers
|
||||
const char PrintIconMarker = 0x4;
|
||||
const char PrinterIconMarker = 0x5;
|
||||
const char PrinterSlaIconMarker = 0x6;
|
||||
const char FilamentIconMarker = 0x7;
|
||||
const char MaterialIconMarker = 0x8;
|
||||
const char CloseNotifButton = 0xB;
|
||||
const char CloseNotifHoverButton = 0xC;
|
||||
const char MinimalizeButton = 0xE;
|
||||
const char MinimalizeHoverButton = 0xF;
|
||||
const char WarningMarker = 0x10;
|
||||
const char ErrorMarker = 0x11;
|
||||
const char EjectButton = 0x12;
|
||||
const char EjectHoverButton = 0x13;
|
||||
const char CancelButton = 0x14;
|
||||
const char CancelHoverButton = 0x15;
|
||||
const char VarLayerHeightMarker = 0x16;
|
||||
const wchar_t PrintIconMarker = 0x4;
|
||||
const wchar_t PrinterIconMarker = 0x5;
|
||||
const wchar_t PrinterSlaIconMarker = 0x6;
|
||||
const wchar_t FilamentIconMarker = 0x7;
|
||||
const wchar_t MaterialIconMarker = 0x8;
|
||||
const wchar_t CloseNotifButton = 0xB;
|
||||
const wchar_t CloseNotifHoverButton = 0xC;
|
||||
const wchar_t MinimalizeButton = 0xE;
|
||||
const wchar_t MinimalizeHoverButton = 0xF;
|
||||
const wchar_t WarningMarker = 0x10;
|
||||
const wchar_t ErrorMarker = 0x11;
|
||||
const wchar_t EjectButton = 0x12;
|
||||
const wchar_t EjectHoverButton = 0x13;
|
||||
const wchar_t CancelButton = 0x14;
|
||||
const wchar_t CancelHoverButton = 0x15;
|
||||
const wchar_t VarLayerHeightMarker = 0x16;
|
||||
|
||||
const char RightArrowButton = 0x18;
|
||||
const char RightArrowHoverButton = 0x19;
|
||||
const char PreferencesButton = 0x1A;
|
||||
const char PreferencesHoverButton = 0x1B;
|
||||
const char SinkingObjectMarker = 0x1C;
|
||||
const char CustomSupportsMarker = 0x1D;
|
||||
const char CustomSeamMarker = 0x1E;
|
||||
const char MmuSegmentationMarker = 0x1F;
|
||||
|
||||
const wchar_t RightArrowButton = 0x18;
|
||||
const wchar_t RightArrowHoverButton = 0x19;
|
||||
const wchar_t PreferencesButton = 0x1A;
|
||||
const wchar_t PreferencesHoverButton = 0x1B;
|
||||
const wchar_t SinkingObjectMarker = 0x1C;
|
||||
const wchar_t CustomSupportsMarker = 0x1D;
|
||||
const wchar_t CustomSeamMarker = 0x1E;
|
||||
const wchar_t MmuSegmentationMarker = 0x1F;
|
||||
const wchar_t DocumentationButton = 0x2600;
|
||||
const wchar_t DocumentationHoverButton = 0x2601;
|
||||
const wchar_t ClippyMarker = 0x2602;
|
||||
|
||||
|
||||
// void MyFunction(const char* name, const MyMatrix44& v);
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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()};
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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();
|
||||
}
|
||||
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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.);
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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();
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
|
|
|
@ -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 = [§ion, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "alias")
|
||||
alias_name = kvp.second.data();
|
||||
else if (kvp.first == "renamed_from") {
|
||||
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
|
||||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
try {
|
||||
auto parse_config_section = [§ion, &alias_name, &renamed_from, &substitution_context, &path](DynamicPrintConfig &config) {
|
||||
substitution_context.substitutions.clear();
|
||||
for (auto &kvp : section.second) {
|
||||
if (kvp.first == "alias")
|
||||
alias_name = kvp.second.data();
|
||||
else if (kvp.first == "renamed_from") {
|
||||
if (! unescape_strings_cstyle(kvp.second.data(), renamed_from)) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Error in a Vendor Config Bundle \"" << path << "\": The preset \"" <<
|
||||
section.first << "\" contains invalid \"renamed_from\" key, which is being ignored.";
|
||||
}
|
||||
}
|
||||
// Throws on parsing error. For system presets, no substituion is being done, but an exception is thrown.
|
||||
config.set_deserialize(kvp.first, kvp.second.data(), substitution_context);
|
||||
}
|
||||
};
|
||||
if (presets == &this->printers) {
|
||||
// Select the default config based on the printer_technology field extracted from kvp.
|
||||
DynamicPrintConfig config_src;
|
||||
parse_config_section(config_src);
|
||||
default_config = &presets->default_preset_for(config_src).config;
|
||||
config = *default_config;
|
||||
config.apply(config_src);
|
||||
} else {
|
||||
default_config = &presets->default_preset().config;
|
||||
config = *default_config;
|
||||
parse_config_section(config);
|
||||
}
|
||||
};
|
||||
if (presets == &this->printers) {
|
||||
// Select the default config based on the printer_technology field extracted from kvp.
|
||||
DynamicPrintConfig config_src;
|
||||
parse_config_section(config_src);
|
||||
default_config = &presets->default_preset_for(config_src).config;
|
||||
config = *default_config;
|
||||
config.apply(config_src);
|
||||
} else {
|
||||
default_config = &presets->default_preset().config;
|
||||
config = *default_config;
|
||||
parse_config_section(config);
|
||||
} catch (const ConfigurationError &e) {
|
||||
throw ConfigurationError(format("Invalid configuration bundle \"%1%\", section [%2%]: ", path, section.first) + e.what());
|
||||
}
|
||||
Preset::normalize(config);
|
||||
// Report configuration fields, which are misplaced into a wrong group.
|
||||
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
#include "QuadricEdgeCollapse.hpp"
|
||||
#include <tuple>
|
||||
#include <optional>
|
||||
#include "MutablePriorityQueue.hpp"
|
||||
#include "SimplifyMeshImpl.hpp"
|
||||
#include <tbb/parallel_for.h>
|
||||
|
|
|
@ -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; }
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
|
|
|
@ -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__
|
||||
|
|
|
@ -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 = """; break;
|
||||
case '\'': replacement = "'"; break;
|
||||
case '&': replacement = "&"; break;
|
||||
case '<': replacement = "<"; break;
|
||||
case '>': replacement = ">"; break;
|
||||
case '<': replacement = is_marked ? "<" :"<"; break;
|
||||
case '>': replacement = is_marked ? ">" :">"; break;
|
||||
default: break;
|
||||
}
|
||||
|
||||
|
|
12
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop
Executable file
12
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.desktop
Executable file
|
@ -0,0 +1,12 @@
|
|||
[Desktop Entry]
|
||||
Name=PrusaSlicer
|
||||
GenericName=3D Printing Software
|
||||
Icon=com.prusa3d.PrusaSlicer
|
||||
Exec=prusa-slicer %F
|
||||
Terminal=false
|
||||
Type=Application
|
||||
MimeType=model/stl;model/x-wavefront-obj;model/3mf;model/x-geomview-off;application/x-amf;
|
||||
Categories=Graphics;3DGraphics;Engineering;
|
||||
Keywords=3D;Printing;Slicer;slice;3D;printer;convert;gcode;stl;obj;amf;SLA
|
||||
StartupNotify=false
|
||||
StartupWMClass=prusa-slicer
|
62
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml
Executable file
62
src/platform/unix/flatpak/com.prusa3d.PrusaSlicer.metainfo.xml
Executable file
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<component type="desktop">
|
||||
<id>com.prusa3d.PrusaSlicer</id>
|
||||
<launchable type="desktop-id">com.prusa3d.PrusaSlicer.desktop</launchable>
|
||||
<provides>
|
||||
<id>prusa-slicer.desktop</id>
|
||||
</provides>
|
||||
<name>PrusaSlicer</name>
|
||||
<summary>Powerful 3D printing slicer optimized for Prusa printers</summary>
|
||||
<metadata_license>0BSD</metadata_license>
|
||||
<project_license>AGPL-3.0-only</project_license>
|
||||
<description>
|
||||
<p>
|
||||
PrusaSlicer takes 3D models (STL, OBJ, AMF) and converts them into G-code
|
||||
instructions for FFF printers or PNG layers for mSLA 3D printers. It's
|
||||
compatible with any modern printer based on the RepRap toolchain, including all
|
||||
those based on the Marlin, Prusa, Sprinter and Repetier firmware. It also works
|
||||
with Mach3, LinuxCNC and Machinekit controllers.
|
||||
</p>
|
||||
<p>
|
||||
PrusaSlicer is based on Slic3r by Alessandro Ranelucci and the RepRap community.
|
||||
</p>
|
||||
<p>
|
||||
What are some of PrusaSlicer's main features?
|
||||
</p>
|
||||
<ul>
|
||||
<li>multi-platform (Linux/Mac/Win) and packaged as standalone-app with no dependencies required</li>
|
||||
<li>complete command-line interface to use it with no GUI</li>
|
||||
<li>multi-material (multiple extruders) object printing</li>
|
||||
<li>multiple G-code flavors supported (RepRap, Makerbot, Mach3, Machinekit etc.)</li>
|
||||
<li>ability to plate multiple objects having distinct print settings</li>
|
||||
<li>multithread processing</li>
|
||||
<li>STL auto-repair (tolerance for broken models)</li>
|
||||
<li>wide automated unit testing</li>
|
||||
</ul>
|
||||
</description>
|
||||
<url type="homepage">https://www.prusa3d.com/prusaslicer/</url>
|
||||
<url type="help">https://help.prusa3d.com</url>
|
||||
<url type="bugtracker">https://github.com/prusa3d/PrusaSlicer/issues</url>
|
||||
<screenshots>
|
||||
<screenshot type="default">
|
||||
<image>https://user-images.githubusercontent.com/590307/78981854-24d07580-7b21-11ea-9441-77923534a659.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://user-images.githubusercontent.com/590307/78981860-2863fc80-7b21-11ea-8c2d-8ff79ced2578.png</image>
|
||||
</screenshot>
|
||||
<screenshot>
|
||||
<image>https://user-images.githubusercontent.com/590307/78981862-28fc9300-7b21-11ea-9b0d-d03e16b709d3.png</image>
|
||||
</screenshot>
|
||||
</screenshots>
|
||||
<content_rating type="oars-1.1" />
|
||||
<releases>
|
||||
<release version="2.2.0" date="2020-03-21">
|
||||
<description>
|
||||
<p>This is final release of PrusaSlicer 2.2.0 introducing SLA hollowing and hole drilling, support for 3rd party printer vendors, 3Dconnexion support,
|
||||
automatic variable layer height, macOS dark mode support, greatly improved ColorPrint feature and much, much more.
|
||||
Several bugs found in the previous release candidate are fixed in this final release. See the respective change logs of the previous releases for all the
|
||||
new features, improvements and bugfixes in the 2.2.0 series.</p>
|
||||
</description>
|
||||
</release>
|
||||
</releases>
|
||||
</component>
|
|
@ -8,14 +8,23 @@
|
|||
#include <boost/property_tree/ini_parser.hpp>
|
||||
#include <boost/property_tree/ptree_fwd.hpp>
|
||||
#include <boost/filesystem/operations.hpp>
|
||||
#include <boost/log/trivial.hpp>
|
||||
|
||||
#include "libslic3r/PresetBundle.hpp"
|
||||
#include "libslic3r/format.hpp"
|
||||
#include "libslic3r/libslic3r.h"
|
||||
#include "libslic3r/Time.hpp"
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/FileParserError.hpp"
|
||||
#include "libslic3r/Utils.hpp"
|
||||
|
||||
#include "../GUI/GUI.hpp"
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
#include "../GUI/I18N.hpp"
|
||||
#include "../GUI/MainFrame.hpp"
|
||||
|
||||
#include <wx/richmsgdlg.h>
|
||||
|
||||
#define SLIC3R_SNAPSHOTS_DIR "snapshots"
|
||||
#define SLIC3R_SNAPSHOT_FILE "snapshot.ini"
|
||||
|
||||
|
@ -358,11 +367,12 @@ static void copy_config_dir_single_level(const boost::filesystem::path &path_src
|
|||
{
|
||||
if (! boost::filesystem::is_directory(path_dst) &&
|
||||
! boost::filesystem::create_directory(path_dst))
|
||||
throw Slic3r::RuntimeError(std::string("Slic3r was unable to create a directory at ") + path_dst.string());
|
||||
throw Slic3r::RuntimeError(std::string("PrusaSlicer was unable to create a directory at ") + path_dst.string());
|
||||
|
||||
for (auto &dir_entry : boost::filesystem::directory_iterator(path_src))
|
||||
if (Slic3r::is_ini_file(dir_entry))
|
||||
boost::filesystem::copy_file(dir_entry.path(), path_dst / dir_entry.path().filename(), boost::filesystem::copy_option::overwrite_if_exists);
|
||||
if (std::string error_message; copy_file(dir_entry.path().string(), (path_dst / dir_entry.path().filename()).string(), error_message, false) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(format("Failed copying \"%1%\" to \"%2%\": %3%", path_src.string(), path_dst.string(), error_message));
|
||||
}
|
||||
|
||||
static void delete_existing_ini_files(const boost::filesystem::path &path)
|
||||
|
@ -413,7 +423,7 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||
++ it;
|
||||
// Read the active config bundle, parse the config version.
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly);
|
||||
bundle.load_configbundle((data_dir / "vendor" / (cfg.name + ".ini")).string(), PresetBundle::LoadConfigBundleAttribute::LoadVendorOnly, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
for (const auto &vp : bundle.vendors)
|
||||
if (vp.second.id == cfg.name)
|
||||
cfg.version.config_version = vp.second.config_version;
|
||||
|
@ -433,14 +443,27 @@ const Snapshot& SnapshotDB::take_snapshot(const AppConfig &app_config, Snapshot:
|
|||
}
|
||||
|
||||
boost::filesystem::path snapshot_dir = snapshot_db_dir / snapshot.id;
|
||||
boost::filesystem::create_directory(snapshot_dir);
|
||||
|
||||
// Backup the presets.
|
||||
for (const char *subdir : snapshot_subdirs)
|
||||
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
try {
|
||||
boost::filesystem::create_directory(snapshot_dir);
|
||||
|
||||
// Backup the presets.
|
||||
for (const char *subdir : snapshot_subdirs)
|
||||
copy_config_dir_single_level(data_dir / subdir, snapshot_dir / subdir);
|
||||
snapshot.save_ini((snapshot_dir / "snapshot.ini").string());
|
||||
assert(m_snapshots.empty() || m_snapshots.back().time_captured <= snapshot.time_captured);
|
||||
m_snapshots.emplace_back(std::move(snapshot));
|
||||
} catch (...) {
|
||||
if (boost::filesystem::is_directory(snapshot_dir)) {
|
||||
try {
|
||||
// Clean up partially copied snapshot.
|
||||
boost::filesystem::remove_all(snapshot_dir);
|
||||
} catch (...) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed taking snapshot and failed removing the snapshot directory " << snapshot_dir;
|
||||
}
|
||||
}
|
||||
throw;
|
||||
}
|
||||
return m_snapshots.back();
|
||||
}
|
||||
|
||||
|
@ -551,6 +574,32 @@ SnapshotDB& SnapshotDB::singleton()
|
|||
return instance;
|
||||
}
|
||||
|
||||
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment)
|
||||
{
|
||||
try {
|
||||
return &SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
|
||||
} catch (std::exception &err) {
|
||||
show_error(static_cast<wxWindow*>(wxGetApp().mainframe),
|
||||
_L("Taking a configuration snapshot failed.") + "\n\n" + from_u8(err.what()));
|
||||
return nullptr;
|
||||
}
|
||||
}
|
||||
|
||||
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message)
|
||||
{
|
||||
try {
|
||||
SnapshotDB::singleton().take_snapshot(app_config, reason, comment);
|
||||
return true;
|
||||
} catch (std::exception &err) {
|
||||
wxRichMessageDialog dlg(static_cast<wxWindow*>(wxGetApp().mainframe),
|
||||
_L("PrusaSlicer has encountered an error while taking a configuration snapshot.") + "\n\n" + from_u8(err.what()) + "\n\n" + from_u8(message),
|
||||
_L("PrusaSlicer error"),
|
||||
wxYES_NO);
|
||||
dlg.SetYesNoLabels(_L("Continue"), _L("Abort"));
|
||||
return dlg.ShowModal() == wxID_YES;
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -127,6 +127,13 @@ private:
|
|||
std::vector<Snapshot> m_snapshots;
|
||||
};
|
||||
|
||||
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report an error and return nullptr.
|
||||
const Snapshot* take_config_snapshot_report_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment);
|
||||
|
||||
// Take snapshot on SnapshotDB::singleton(). If taking snapshot fails, report "message", and present a "Continue" or "Abort" buttons to respond.
|
||||
// Return true on success and on "Continue" to continue with the process (for example installation of presets).
|
||||
bool take_config_snapshot_cancel_on_error(const AppConfig &app_config, Snapshot::Reason reason, const std::string &comment, const std::string &message);
|
||||
|
||||
} // namespace Config
|
||||
} // namespace GUI
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -843,12 +843,13 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
volume.first->set_render_color();
|
||||
|
||||
// render sinking contours of non-hovered volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
||||
shader->stop_using();
|
||||
volume.first->render_sinking_contours();
|
||||
shader->start_using();
|
||||
}
|
||||
if (m_show_sinking_contours)
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
volume.first->hover == GLVolume::HS_None && !volume.first->force_sinking_contours) {
|
||||
shader->stop_using();
|
||||
volume.first->render_sinking_contours();
|
||||
shader->start_using();
|
||||
}
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
@ -887,17 +888,18 @@ void GLVolumeCollection::render(GLVolumeCollection::ERenderType type, bool disab
|
|||
glsafe(::glDisableClientState(GL_NORMAL_ARRAY));
|
||||
}
|
||||
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
// render sinking contours of hovered/displaced volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
|
||||
shader->stop_using();
|
||||
glsafe(::glDepthFunc(GL_ALWAYS));
|
||||
volume.first->render_sinking_contours();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
shader->start_using();
|
||||
if (m_show_sinking_contours)
|
||||
for (GLVolumeWithIdAndZ& volume : to_render) {
|
||||
// render sinking contours of hovered/displaced volumes
|
||||
if (volume.first->is_sinking() && !volume.first->is_below_printbed() &&
|
||||
(volume.first->hover != GLVolume::HS_None || volume.first->force_sinking_contours)) {
|
||||
shader->stop_using();
|
||||
glsafe(::glDepthFunc(GL_ALWAYS));
|
||||
volume.first->render_sinking_contours();
|
||||
glsafe(::glDepthFunc(GL_LESS));
|
||||
shader->start_using();
|
||||
}
|
||||
}
|
||||
}
|
||||
#else
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
glsafe(::glEnableClientState(GL_NORMAL_ARRAY));
|
||||
|
|
|
@ -539,6 +539,7 @@ private:
|
|||
};
|
||||
|
||||
Slope m_slope;
|
||||
bool m_show_sinking_contours = false;
|
||||
|
||||
public:
|
||||
GLVolumePtrs volumes;
|
||||
|
@ -608,6 +609,7 @@ public:
|
|||
float get_slope_normal_z() const { return m_slope.normal_z; }
|
||||
void set_slope_normal_z(float normal_z) { m_slope.normal_z = normal_z; }
|
||||
void set_default_slope_normal_z() { m_slope.normal_z = -::cos(Geometry::deg2rad(90.0f - 45.0f)); }
|
||||
void set_show_sinking_contours(bool show) { m_show_sinking_contours = show; }
|
||||
|
||||
// returns true if all the volumes are completely contained in the print volume
|
||||
// returns the containment state in the given out_state, if non-null
|
||||
|
|
|
@ -154,54 +154,7 @@ void BackgroundSlicingProcess::process_fff()
|
|||
if (this->set_step_started(bspsGCodeFinalize)) {
|
||||
if (! m_export_path.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
|
||||
// let the gcode window to unmap the temporary .gcode file (m_temp_output_path)
|
||||
// because the scripts may want to modify it
|
||||
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
|
||||
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
run_post_process_scripts(m_temp_output_path, m_fff_print->full_print_config());
|
||||
|
||||
// let the gcode window to reload and remap the temporary .gcode file (m_temp_output_path)
|
||||
GUI::wxGetApp().plater()->start_mapping_gcode_window();
|
||||
|
||||
//FIXME localize the messages
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
std::string error_message;
|
||||
int copy_ret_val = CopyFileResult::SUCCESS;
|
||||
try
|
||||
{
|
||||
copy_ret_val = copy_file(m_temp_output_path, export_path, error_message, m_export_path_on_removable_media);
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
case CopyFileResult::FAIL_COPY_FILE:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_FILES_DIFFERENT:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_RENAMING:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % m_temp_output_path % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
finalize_gcode();
|
||||
} else if (! m_upload_job.empty()) {
|
||||
wxQueueEvent(GUI::wxGetApp().mainframe->m_plater, new wxCommandEvent(m_event_export_began_id));
|
||||
prepare_upload();
|
||||
|
@ -621,8 +574,11 @@ Print::ApplyStatus BackgroundSlicingProcess::apply(const Model &model, const Dyn
|
|||
// Some FFF status was invalidated, and the G-code was not exported yet.
|
||||
// Let the G-code preview UI know that the final G-code preview is not valid.
|
||||
// In addition, this early memory deallocation reduces memory footprint.
|
||||
if (m_gcode_result != nullptr)
|
||||
if (m_gcode_result != nullptr) {
|
||||
//FIXME calling platter from here is not a staple of a good architecture.
|
||||
GUI::wxGetApp().plater()->stop_mapping_gcode_window();
|
||||
m_gcode_result->reset();
|
||||
}
|
||||
}
|
||||
return invalidated;
|
||||
}
|
||||
|
@ -698,12 +654,73 @@ bool BackgroundSlicingProcess::invalidate_all_steps()
|
|||
return m_step_state.invalidate_all([this](){ this->stop_internal(); });
|
||||
}
|
||||
|
||||
// G-code is generated in m_temp_output_path.
|
||||
// Optionally run a post-processing script on a copy of m_temp_output_path.
|
||||
// Copy the final G-code to target location (possibly a SD card, if it is a removable media, then verify that the file was written without an error).
|
||||
void BackgroundSlicingProcess::finalize_gcode()
|
||||
{
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
|
||||
// Perform the final post-processing of the export path by applying the print statistics over the file name.
|
||||
std::string export_path = m_fff_print->print_statistics().finalize_output_path(m_export_path);
|
||||
std::string output_path = m_temp_output_path;
|
||||
// Both output_path and export_path ar in-out parameters.
|
||||
// If post processed, output_path will differ from m_temp_output_path as run_post_process_scripts() will make a copy of the G-code to not
|
||||
// collide with the G-code viewer memory mapping of the unprocessed G-code. G-code viewer maps unprocessed G-code, because m_gcode_result
|
||||
// is calculated for the unprocessed G-code and it references lines in the memory mapped G-code file by line numbers.
|
||||
// export_path may be changed by the post-processing script as well if the post processing script decides so, see GH #6042.
|
||||
bool post_processed = run_post_process_scripts(output_path, true, "File", export_path, m_fff_print->full_print_config());
|
||||
auto remove_post_processed_temp_file = [post_processed, &output_path]() {
|
||||
if (post_processed)
|
||||
try {
|
||||
boost::filesystem::remove(output_path);
|
||||
} catch (const std::exception &ex) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Failed to remove temp file " << output_path << ": " << ex.what();
|
||||
}
|
||||
};
|
||||
|
||||
//FIXME localize the messages
|
||||
std::string error_message;
|
||||
int copy_ret_val = CopyFileResult::SUCCESS;
|
||||
try
|
||||
{
|
||||
copy_ret_val = copy_file(output_path, export_path, error_message, m_export_path_on_removable_media);
|
||||
remove_post_processed_temp_file();
|
||||
}
|
||||
catch (...)
|
||||
{
|
||||
remove_post_processed_temp_file();
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
}
|
||||
switch (copy_ret_val) {
|
||||
case CopyFileResult::SUCCESS: break; // no error
|
||||
case CopyFileResult::FAIL_COPY_FILE:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. Maybe the SD card is write locked?\nError message: %1%"))) % error_message).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_FILES_DIFFERENT:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code to the output G-code failed. There might be problem with target device, please try exporting again or using different device. The corrupted output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_RENAMING:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Renaming of the G-code after copying to the selected destination folder has failed. Current path is %1%.tmp. Please try exporting again."))) % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_ORIGIN_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the original code at %1% couldn't be opened during copy check. The output G-code is at %2%.tmp."))) % output_path % export_path).str());
|
||||
break;
|
||||
case CopyFileResult::FAIL_CHECK_TARGET_NOT_OPENED:
|
||||
throw Slic3r::ExportError((boost::format(_utf8(L("Copying of the temporary G-code has finished but the exported code couldn't be opened during copy check. The output G-code is at %1%.tmp."))) % export_path).str());
|
||||
break;
|
||||
default:
|
||||
throw Slic3r::ExportError(_utf8(L("Unknown error occured during exporting G-code.")));
|
||||
BOOST_LOG_TRIVIAL(error) << "Unexpected fail code(" << (int)copy_ret_val << ") durring copy_file() to " << export_path << ".";
|
||||
break;
|
||||
}
|
||||
|
||||
m_print->set_status(100, (boost::format(_utf8(L("G-code file exported to %1%"))) % export_path).str());
|
||||
}
|
||||
|
||||
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||
void BackgroundSlicingProcess::prepare_upload()
|
||||
{
|
||||
// A print host upload job has been scheduled, enqueue it to the printhost job queue
|
||||
|
||||
// XXX: is fs::path::string() right?
|
||||
|
||||
// Generate a unique temp path to which the gcode/zip file is copied/exported
|
||||
boost::filesystem::path source_path = boost::filesystem::temp_directory_path()
|
||||
/ boost::filesystem::unique_path("." SLIC3R_APP_KEY ".upload.%%%%-%%%%-%%%%-%%%%");
|
||||
|
@ -711,11 +728,15 @@ void BackgroundSlicingProcess::prepare_upload()
|
|||
if (m_print == m_fff_print) {
|
||||
m_print->set_status(95, _utf8(L("Running post-processing scripts")));
|
||||
std::string error_message;
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS) {
|
||||
if (copy_file(m_temp_output_path, source_path.string(), error_message) != SUCCESS)
|
||||
throw Slic3r::RuntimeError(_utf8(L("Copying of the temporary G-code to the output G-code failed")));
|
||||
}
|
||||
run_post_process_scripts(source_path.string(), m_fff_print->full_print_config());
|
||||
m_upload_job.upload_data.upload_path = m_fff_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
// Make a copy of the source path, as run_post_process_scripts() is allowed to change it when making a copy of the source file
|
||||
// (not here, but when the final target is a file).
|
||||
std::string source_path_str = source_path.string();
|
||||
std::string output_name_str = m_upload_job.upload_data.upload_path.string();
|
||||
if (run_post_process_scripts(source_path_str, false, m_upload_job.printhost->get_name(), output_name_str, m_fff_print->full_print_config()))
|
||||
m_upload_job.upload_data.upload_path = output_name_str;
|
||||
} else {
|
||||
m_upload_job.upload_data.upload_path = m_sla_print->print_statistics().finalize_output_path(m_upload_job.upload_data.upload_path.string());
|
||||
|
||||
|
|
|
@ -216,9 +216,9 @@ private:
|
|||
Print *m_fff_print = nullptr;
|
||||
SLAPrint *m_sla_print = nullptr;
|
||||
// Data structure, to which the G-code export writes its annotations.
|
||||
GCodeProcessor::Result *m_gcode_result = nullptr;
|
||||
GCodeProcessor::Result *m_gcode_result = nullptr;
|
||||
// Callback function, used to write thumbnails into gcode.
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
ThumbnailsGeneratorCallback m_thumbnail_cb = nullptr;
|
||||
SL1Archive m_sla_archive;
|
||||
// Temporary G-code, there is one defined for the BackgroundSlicingProcess, differentiated from the other processes by a process ID.
|
||||
std::string m_temp_output_path;
|
||||
|
@ -262,6 +262,7 @@ private:
|
|||
bool invalidate_all_steps();
|
||||
// If the background processing stop was requested, throw CanceledException.
|
||||
void throw_if_canceled() const { if (m_print->canceled()) throw CanceledException(); }
|
||||
void finalize_gcode();
|
||||
void prepare_upload();
|
||||
// To be executed at the background thread.
|
||||
ThumbnailsList render_thumbnails(const ThumbnailsParams ¶ms);
|
||||
|
|
|
@ -65,7 +65,9 @@ bool Bundle::load(fs::path source_path, bool ais_in_resources, bool ais_prusa_bu
|
|||
this->is_prusa_bundle = ais_prusa_bundle;
|
||||
|
||||
std::string path_string = source_path.string();
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
||||
auto [config_substitutions, presets_loaded] = preset_bundle->load_configbundle(
|
||||
path_string, PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
UNUSED(config_substitutions);
|
||||
// No substitutions shall be reported when loading a system config bundle, no substitutions are allowed.
|
||||
assert(config_substitutions.empty());
|
||||
|
@ -2451,7 +2453,7 @@ bool ConfigWizard::priv::check_and_install_missing_materials(Technology technolo
|
|||
return true;
|
||||
}
|
||||
|
||||
void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
bool ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater)
|
||||
{
|
||||
const auto enabled_vendors = appconfig_new.vendors();
|
||||
|
||||
|
@ -2506,14 +2508,14 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
break;
|
||||
}
|
||||
|
||||
if (snapshot) {
|
||||
SnapshotDB::singleton().take_snapshot(*app_config, snapshot_reason);
|
||||
}
|
||||
if (snapshot && ! take_config_snapshot_cancel_on_error(*app_config, snapshot_reason, "", _u8L("Continue with applying configuration changes?")))
|
||||
return false;
|
||||
|
||||
if (install_bundles.size() > 0) {
|
||||
// Install bundles from resources.
|
||||
// Don't create snapshot - we've already done that above if applicable.
|
||||
updater->install_bundles_rsrc(std::move(install_bundles), false);
|
||||
if (! updater->install_bundles_rsrc(std::move(install_bundles), false))
|
||||
return false;
|
||||
} else {
|
||||
BOOST_LOG_TRIVIAL(info) << "No bundles need to be installed from resources";
|
||||
}
|
||||
|
@ -2590,7 +2592,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
// Reloading the configs after some modifications were done to PrusaSlicer.ini.
|
||||
// Just perform the substitutions silently, as the substitutions were already presented to the user on application start-up
|
||||
// and the Wizard shall not create any new values that would require substitution.
|
||||
PresetsConfigSubstitutions substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent, preferred_model);
|
||||
// Throw on substitutions in system profiles, as the system profiles provided over the air should be compatible with this PrusaSlicer version.
|
||||
preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem, preferred_model);
|
||||
|
||||
if (page_custom->custom_wanted()) {
|
||||
page_firmware->apply_custom_config(*custom_config);
|
||||
|
@ -2604,6 +2607,8 @@ void ConfigWizard::priv::apply_config(AppConfig *app_config, PresetBundle *prese
|
|||
|
||||
// Update the selections from the compatibilty.
|
||||
preset_bundle->export_selections(*app_config);
|
||||
|
||||
return true;
|
||||
}
|
||||
void ConfigWizard::priv::update_presets_in_config(const std::string& section, const std::string& alias_key, bool add)
|
||||
{
|
||||
|
@ -2814,7 +2819,8 @@ bool ConfigWizard::run(RunReason reason, StartPage start_page)
|
|||
p->set_start_page(start_page);
|
||||
|
||||
if (ShowModal() == wxID_OK) {
|
||||
p->apply_config(app.app_config, app.preset_bundle, app.preset_updater);
|
||||
if (! p->apply_config(app.app_config, app.preset_bundle, app.preset_updater))
|
||||
return false;
|
||||
app.app_config->set_legacy_datadir(false);
|
||||
app.update_mode();
|
||||
app.obj_manipul()->update_ui_from_settings();
|
||||
|
|
|
@ -611,7 +611,7 @@ struct ConfigWizard::priv
|
|||
|
||||
bool on_bnt_finish();
|
||||
bool check_and_install_missing_materials(Technology technology, const std::string &only_for_model_id = std::string());
|
||||
void apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
bool apply_config(AppConfig *app_config, PresetBundle *preset_bundle, const PresetUpdater *updater);
|
||||
// #ys_FIXME_alise
|
||||
void update_presets_in_config(const std::string& section, const std::string& alias_key, bool add);
|
||||
#ifdef __linux__
|
||||
|
|
|
@ -11,6 +11,7 @@
|
|||
#include "GUI_Utils.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "Tab.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
|
||||
#include <wx/button.h>
|
||||
#include <wx/dialog.h>
|
||||
|
@ -383,7 +384,11 @@ void Control::SetTicksValues(const Info& custom_gcode_per_print_z)
|
|||
// Switch to the "Feature type"/"Tool" from the very beginning of a new object slicing after deleting of the old one
|
||||
post_ticks_changed_event();
|
||||
|
||||
if (custom_gcode_per_print_z.mode)
|
||||
// init extruder sequence in respect to the extruders count
|
||||
if (m_ticks.empty())
|
||||
m_extruders_sequence.init(m_extruder_colors.size());
|
||||
|
||||
if (custom_gcode_per_print_z.mode && !custom_gcode_per_print_z.gcodes.empty())
|
||||
m_ticks.mode = custom_gcode_per_print_z.mode;
|
||||
|
||||
Refresh();
|
||||
|
@ -438,7 +443,7 @@ void Control::SetModeAndOnlyExtruder(const bool is_one_extruder_printed_model, c
|
|||
m_mode = !is_one_extruder_printed_model ? MultiExtruder :
|
||||
only_extruder < 0 ? SingleExtruder :
|
||||
MultiAsSingle;
|
||||
if (!m_ticks.mode)
|
||||
if (!m_ticks.mode || (m_ticks.empty() && m_ticks.mode != m_mode))
|
||||
m_ticks.mode = m_mode;
|
||||
m_only_extruder = only_extruder;
|
||||
|
||||
|
@ -545,7 +550,8 @@ bool Control::is_wipe_tower_layer(int tick) const
|
|||
return false;
|
||||
if (tick == 0 || (tick == (int)m_values.size() - 1 && m_values[tick] > m_values[tick - 1]))
|
||||
return false;
|
||||
if (m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1])
|
||||
if ((m_values[tick - 1] == m_values[tick + 1] && m_values[tick] < m_values[tick + 1]) ||
|
||||
(tick > 0 && m_values[tick] < m_values[tick - 1]) ) // if there is just one wiping on the layer
|
||||
return true;
|
||||
|
||||
return false;
|
||||
|
@ -1077,7 +1083,9 @@ void Control::draw_ruler(wxDC& dc)
|
|||
{
|
||||
if (m_values.empty())
|
||||
return;
|
||||
m_ruler.update(this->GetParent(), m_values, get_scroll_step());
|
||||
// When "No sparce layer" is enabled, use m_layers_values for ruler update.
|
||||
// Because of m_values has duplicate values in this case.
|
||||
m_ruler.update(this->GetParent(), m_layers_values.empty() ? m_values : m_layers_values, get_scroll_step());
|
||||
|
||||
int height, width;
|
||||
get_size(&width, &height);
|
||||
|
@ -1588,7 +1596,7 @@ void Control::append_change_extruder_menu_item(wxMenu* menu, bool switch_current
|
|||
|
||||
append_submenu(menu, change_extruder_menu, wxID_ANY, change_extruder_menu_name, _L("Use another extruder"),
|
||||
active_extruders[1] > 0 ? "edit_uni" : "change_extruder",
|
||||
[this]() {return m_mode == MultiAsSingle; }, GUI::wxGetApp().plater());
|
||||
[this]() {return m_mode == MultiAsSingle && !GUI::wxGetApp().obj_list()->has_paint_on_segmentation(); }, GUI::wxGetApp().plater());
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -180,6 +180,13 @@ struct ExtrudersSequence
|
|||
return;// last item can't be deleted
|
||||
extruders.erase(extruders.begin() + pos);
|
||||
}
|
||||
|
||||
void init(size_t extruders_count)
|
||||
{
|
||||
extruders.clear();
|
||||
for (size_t extruder = 0; extruder < extruders_count; extruder++)
|
||||
extruders.push_back(extruder);
|
||||
}
|
||||
};
|
||||
|
||||
class Control : public wxControl
|
||||
|
|
|
@ -2,6 +2,7 @@
|
|||
#include "wxExtensions.hpp"
|
||||
#include "GUI.hpp"
|
||||
#include "I18N.hpp"
|
||||
#include "BitmapComboBox.hpp"
|
||||
|
||||
#include <wx/dc.h>
|
||||
#ifdef wxHAS_GENERIC_DATAVIEWCTRL
|
||||
|
@ -296,7 +297,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
|
|||
DataViewBitmapText data;
|
||||
data << value;
|
||||
|
||||
#ifdef _WIN32
|
||||
Slic3r::GUI::BitmapComboBox* c_editor = new Slic3r::GUI::BitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#else
|
||||
auto c_editor = new wxBitmapComboBox(parent, wxID_ANY, wxEmptyString,
|
||||
#endif
|
||||
labelRect.GetTopLeft(), wxSize(labelRect.GetWidth(), -1),
|
||||
0, nullptr , wxCB_READONLY);
|
||||
|
||||
|
@ -310,9 +315,11 @@ wxWindow* BitmapChoiceRenderer::CreateEditorCtrl(wxWindow* parent, wxRect labelR
|
|||
// to avoid event propagation to other sidebar items
|
||||
c_editor->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent& evt) {
|
||||
evt.StopPropagation();
|
||||
#ifdef __linux__
|
||||
// FinishEditing grabs new selection and triggers config update. We better call
|
||||
// it explicitly, automatic update on KILL_FOCUS didn't work on Linux.
|
||||
this->FinishEditing();
|
||||
#endif
|
||||
});
|
||||
|
||||
return c_editor;
|
||||
|
|
|
@ -73,8 +73,6 @@ Field::~Field()
|
|||
{
|
||||
if (m_on_kill_focus)
|
||||
m_on_kill_focus = nullptr;
|
||||
if (m_on_set_focus)
|
||||
m_on_set_focus = nullptr;
|
||||
if (m_on_change)
|
||||
m_on_change = nullptr;
|
||||
if (m_back_to_initial_value)
|
||||
|
@ -156,15 +154,6 @@ void Field::on_kill_focus()
|
|||
m_on_kill_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_set_focus(wxEvent& event)
|
||||
{
|
||||
// to allow the default behavior
|
||||
event.Skip();
|
||||
// call the registered function if it is available
|
||||
if (m_on_set_focus!=nullptr)
|
||||
m_on_set_focus(m_opt_id);
|
||||
}
|
||||
|
||||
void Field::on_change_field()
|
||||
{
|
||||
// std::cerr << "calling Field::_on_change \n";
|
||||
|
@ -500,20 +489,18 @@ void TextCtrl::BUILD() {
|
|||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
||||
if (style == wxTE_PROCESS_ENTER) {
|
||||
if (style & wxTE_PROCESS_ENTER) {
|
||||
temp->Bind(wxEVT_TEXT_ENTER, ([this, temp](wxEvent& e)
|
||||
{
|
||||
#if !defined(__WXGTK__)
|
||||
e.Skip();
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
bEnterPressed = true;
|
||||
EnterPressed enter(this);
|
||||
propagate_value();
|
||||
}), temp->GetId());
|
||||
}
|
||||
|
||||
temp->Bind(wxEVT_SET_FOCUS, ([this](wxEvent& e) { on_set_focus(e); }), temp->GetId());
|
||||
|
||||
temp->Bind(wxEVT_LEFT_DOWN, ([temp](wxEvent& event)
|
||||
{
|
||||
//! to allow the default handling
|
||||
|
@ -530,26 +517,11 @@ void TextCtrl::BUILD() {
|
|||
temp->Bind(wxEVT_KILL_FOCUS, ([this, temp](wxEvent& e)
|
||||
{
|
||||
e.Skip();
|
||||
#ifdef __WXOSX__
|
||||
// OSX issue: For some unknown reason wxEVT_KILL_FOCUS is emitted twice in a row in some cases
|
||||
// (like when information dialog is shown during an update of the option value)
|
||||
// Thus, suppress its second call
|
||||
if (bKilledFocus)
|
||||
return;
|
||||
bKilledFocus = true;
|
||||
#endif // __WXOSX__
|
||||
|
||||
#if !defined(__WXGTK__)
|
||||
temp->GetToolTip()->Enable(true);
|
||||
#endif // __WXGTK__
|
||||
if (bEnterPressed)
|
||||
bEnterPressed = false;
|
||||
else
|
||||
if (!bEnterPressed)
|
||||
propagate_value();
|
||||
#ifdef __WXOSX__
|
||||
// After processing of KILL_FOCUS event we should to invalidate a bKilledFocus flag
|
||||
bKilledFocus = false;
|
||||
#endif // __WXOSX__
|
||||
}), temp->GetId());
|
||||
/*
|
||||
// select all text using Ctrl+A
|
||||
|
@ -851,7 +823,7 @@ void SpinCtrl::BUILD() {
|
|||
bEnterPressed = true;
|
||||
}), temp->GetId());
|
||||
|
||||
temp->Bind(wxEVT_TEXT, ([this](wxCommandEvent e)
|
||||
temp->Bind(wxEVT_TEXT, ([this, temp](wxCommandEvent e)
|
||||
{
|
||||
// # On OSX / Cocoa, wxSpinCtrl::GetValue() doesn't return the new value
|
||||
// # when it was changed from the text control, so the on_change callback
|
||||
|
@ -861,20 +833,28 @@ void SpinCtrl::BUILD() {
|
|||
|
||||
long value;
|
||||
const bool parsed = e.GetString().ToLong(&value);
|
||||
tmp_value = parsed && value >= INT_MIN && value <= INT_MAX ? (int)value : UNDEF_VALUE;
|
||||
|
||||
if (!parsed || value < INT_MIN || value > INT_MAX)
|
||||
tmp_value = UNDEF_VALUE;
|
||||
else {
|
||||
tmp_value = std::min(std::max((int)value, m_opt.min), m_opt.max);
|
||||
#ifdef __WXOSX__
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
if (tmp_value != UNDEF_VALUE) {
|
||||
wxSpinCtrl* spin = static_cast<wxSpinCtrl*>(window);
|
||||
spin->SetValue(tmp_value);
|
||||
|
||||
// Forcibly set the input value for SpinControl, since the value
|
||||
// inserted from the keyboard or clipboard is not updated under OSX
|
||||
temp->SetValue(tmp_value);
|
||||
// But in SetValue() is executed m_text_ctrl->SelectAll(), so
|
||||
// discard this selection and set insertion point to the end of string
|
||||
spin->GetText()->SetInsertionPointEnd();
|
||||
}
|
||||
temp->GetText()->SetInsertionPointEnd();
|
||||
#else
|
||||
// update value for the control only if it was changed in respect to the Min/max values
|
||||
if (tmp_value != (int)value) {
|
||||
temp->SetValue(tmp_value);
|
||||
// But after SetValue() cursor ison the first position
|
||||
// so put it to the end of string
|
||||
int pos = std::to_string(tmp_value).length();
|
||||
temp->SetSelection(pos, pos);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
}), temp->GetId());
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(text_value));
|
||||
|
@ -935,7 +915,7 @@ void Choice::BUILD() {
|
|||
choice_ctrl* temp;
|
||||
if (m_opt.gui_type != ConfigOptionDef::GUIType::undefined && m_opt.gui_type != ConfigOptionDef::GUIType::select_open) {
|
||||
m_is_editable = true;
|
||||
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size);
|
||||
temp = new choice_ctrl(m_parent, wxID_ANY, wxString(""), wxDefaultPosition, size, 0, nullptr, wxTE_PROCESS_ENTER);
|
||||
}
|
||||
else {
|
||||
#ifdef __WXOSX__
|
||||
|
@ -988,46 +968,57 @@ void Choice::BUILD() {
|
|||
temp->Bind(wxEVT_COMBOBOX, [this](wxCommandEvent&) { on_change_field(); }, temp->GetId());
|
||||
|
||||
if (m_is_editable) {
|
||||
temp->Bind(wxEVT_KILL_FOCUS, ([this](wxEvent& e) {
|
||||
temp->Bind(wxEVT_KILL_FOCUS, [this](wxEvent& e) {
|
||||
e.Skip();
|
||||
if (m_opt.type == coStrings) {
|
||||
on_change_field();
|
||||
return;
|
||||
}
|
||||
if (!bEnterPressed)
|
||||
propagate_value();
|
||||
} );
|
||||
|
||||
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
|
||||
switch (m_opt.type) {
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
|
||||
if (old_val == boost::any_cast<std::string>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case coInt:
|
||||
{
|
||||
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
|
||||
if (old_val == boost::any_cast<int>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
|
||||
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
|
||||
return;
|
||||
}
|
||||
}
|
||||
on_change_field();
|
||||
}
|
||||
else
|
||||
on_kill_focus();
|
||||
}), temp->GetId());
|
||||
temp->Bind(wxEVT_TEXT_ENTER, [this, temp](wxEvent& e) {
|
||||
EnterPressed enter(this);
|
||||
propagate_value();
|
||||
} );
|
||||
}
|
||||
|
||||
temp->SetToolTip(get_tooltip_text(temp->GetValue()));
|
||||
}
|
||||
|
||||
void Choice::propagate_value()
|
||||
{
|
||||
if (m_opt.type == coStrings) {
|
||||
on_change_field();
|
||||
return;
|
||||
}
|
||||
|
||||
if (is_defined_input_value<choice_ctrl>(window, m_opt.type)) {
|
||||
switch (m_opt.type) {
|
||||
case coFloatOrPercent:
|
||||
{
|
||||
std::string old_val = !m_value.empty() ? boost::any_cast<std::string>(m_value) : "";
|
||||
if (old_val == boost::any_cast<std::string>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
case coInt:
|
||||
{
|
||||
int old_val = !m_value.empty() ? boost::any_cast<int>(m_value) : 0;
|
||||
if (old_val == boost::any_cast<int>(get_value()))
|
||||
return;
|
||||
break;
|
||||
}
|
||||
default:
|
||||
{
|
||||
double old_val = !m_value.empty() ? boost::any_cast<double>(m_value) : -99999;
|
||||
if (fabs(old_val - boost::any_cast<double>(get_value())) <= 0.0001)
|
||||
return;
|
||||
}
|
||||
}
|
||||
on_change_field();
|
||||
}
|
||||
else
|
||||
on_kill_focus();
|
||||
}
|
||||
|
||||
void Choice::suppress_scroll()
|
||||
{
|
||||
m_suppress_scroll = true;
|
||||
|
@ -1137,6 +1128,15 @@ void Choice::set_value(const boost::any& value, bool change_event)
|
|||
}
|
||||
else
|
||||
field->SetSelection(idx);
|
||||
|
||||
if (!m_value.empty() && m_opt.opt_key == "fill_density") {
|
||||
// If m_value was changed before, then update m_value here too to avoid case
|
||||
// when control's value is already changed from the ConfigManipulation::update_print_fff_config(),
|
||||
// but m_value doesn't respect it.
|
||||
if (double val; text_value.ToDouble(&val))
|
||||
m_value = val;
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case coEnum: {
|
||||
|
|
|
@ -52,10 +52,17 @@ protected:
|
|||
//! in another case we can't unfocused control at all
|
||||
void on_kill_focus();
|
||||
/// Call the attached on_change method.
|
||||
void on_set_focus(wxEvent& event);
|
||||
/// Call the attached on_change method.
|
||||
void on_change_field();
|
||||
|
||||
class EnterPressed {
|
||||
public:
|
||||
EnterPressed(Field* field) :
|
||||
m_parent(field){ m_parent->set_enter_pressed(true); }
|
||||
~EnterPressed() { m_parent->set_enter_pressed(false); }
|
||||
private:
|
||||
Field* m_parent;
|
||||
};
|
||||
|
||||
public:
|
||||
/// Call the attached m_back_to_initial_value method.
|
||||
void on_back_to_initial_value();
|
||||
|
@ -69,9 +76,6 @@ public:
|
|||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_kill_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_kill_focus m_on_set_focus {nullptr};
|
||||
|
||||
/// Function object to store callback passed in from owning object.
|
||||
t_change m_on_change {nullptr};
|
||||
|
||||
|
@ -236,10 +240,6 @@ class TextCtrl : public Field {
|
|||
void change_field_value(wxEvent& event);
|
||||
#endif //__WXGTK__
|
||||
|
||||
#ifdef __WXOSX__
|
||||
bool bKilledFocus = false;
|
||||
#endif // __WXOSX__
|
||||
|
||||
public:
|
||||
TextCtrl(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
TextCtrl(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
|
@ -342,6 +342,7 @@ public:
|
|||
|
||||
class Choice : public Field {
|
||||
using Field::Field;
|
||||
|
||||
public:
|
||||
Choice(const ConfigOptionDef& opt, const t_config_option_key& id) : Field(opt, id) {}
|
||||
Choice(wxWindow* parent, const ConfigOptionDef& opt, const t_config_option_key& id) : Field(parent, opt, id) {}
|
||||
|
@ -349,6 +350,8 @@ public:
|
|||
|
||||
wxWindow* window{ nullptr };
|
||||
void BUILD() override;
|
||||
// Propagate value from field to the OptionGroupe and Config after kill_focus/ENTER
|
||||
void propagate_value();
|
||||
|
||||
/* Under OSX: wxBitmapComboBox->GetWindowStyle() returns some weard value,
|
||||
* so let use a flag, which has TRUE value for a control without wxCB_READONLY style
|
||||
|
|
|
@ -65,6 +65,8 @@ enum {
|
|||
USB_PID_MMU_APP = 4,
|
||||
USB_PID_CW1_BOOT = 7,
|
||||
USB_PID_CW1_APP = 8,
|
||||
USB_PID_CW1S_BOOT = 14,
|
||||
USB_PID_CW1S_APP = 15,
|
||||
};
|
||||
|
||||
// This enum discriminates the kind of information in EVT_AVRDUDE,
|
||||
|
@ -308,7 +310,7 @@ void FirmwareDialog::priv::update_flash_enabled()
|
|||
void FirmwareDialog::priv::load_hex_file(const wxString &path)
|
||||
{
|
||||
hex_file = HexFile(path.wx_str());
|
||||
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1;
|
||||
const bool autodetect = hex_file.device == HexFile::DEV_MM_CONTROL || hex_file.device == HexFile::DEV_CW1 || hex_file.device == HexFile::DEV_CW1S;
|
||||
set_autodetect(autodetect);
|
||||
}
|
||||
|
||||
|
@ -636,6 +638,10 @@ void FirmwareDialog::priv::perform_upload()
|
|||
this->prepare_avr109(Avr109Pid(USB_PID_CW1_BOOT, USB_PID_CW1_APP));
|
||||
break;
|
||||
|
||||
case HexFile::DEV_CW1S:
|
||||
this->prepare_avr109(Avr109Pid(USB_PID_CW1S_BOOT, USB_PID_CW1S_APP));
|
||||
break;
|
||||
|
||||
default:
|
||||
this->prepare_mk2();
|
||||
break;
|
||||
|
@ -767,11 +773,10 @@ const char* FirmwareDialog::priv::avr109_dev_name(Avr109Pid usb_pid) {
|
|||
switch (usb_pid.boot) {
|
||||
case USB_PID_MMU_BOOT:
|
||||
return "Original Prusa MMU 2.0 Control";
|
||||
break;
|
||||
case USB_PID_CW1_BOOT:
|
||||
return "Original Prusa CW1";
|
||||
break;
|
||||
|
||||
case USB_PID_CW1S_BOOT:
|
||||
return "Original Prusa CW1S";
|
||||
default: throw Slic3r::RuntimeError((boost::format("Invalid avr109 device USB PID: %1%") % usb_pid.boot).str());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3455,12 +3455,17 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|||
|
||||
// stores current min_z of instances
|
||||
std::map<std::pair<int, int>, double> min_zs;
|
||||
if (!snapshot_type.empty()) {
|
||||
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
||||
const ModelObject* obj = m_model->objects[i];
|
||||
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
||||
for (int i = 0; i < static_cast<int>(m_model->objects.size()); ++i) {
|
||||
const ModelObject* obj = m_model->objects[i];
|
||||
for (int j = 0; j < static_cast<int>(obj->instances.size()); ++j) {
|
||||
if (snapshot_type.empty() && m_selection.get_object_idx() == i) {
|
||||
// This means we are flattening this object. In that case pretend
|
||||
// that it is not sinking (even if it is), so it is placed on bed
|
||||
// later on (whatever is sinking will be left sinking).
|
||||
min_zs[{ i, j }] = SINKING_Z_THRESHOLD;
|
||||
} else
|
||||
min_zs[{ i, j }] = obj->instance_bounding_box(j).min.z();
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -3502,7 +3507,7 @@ void GLCanvas3D::do_rotate(const std::string& snapshot_type)
|
|||
ModelObject* m = m_model->objects[i.first];
|
||||
const double shift_z = m->get_instance_min_z(i.second);
|
||||
// leave sinking instances as sinking
|
||||
if (min_zs.empty() || min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
|
||||
if (min_zs.find({ i.first, i.second })->second >= SINKING_Z_THRESHOLD || shift_z > SINKING_Z_THRESHOLD) {
|
||||
const Vec3d shift(0.0, 0.0, -shift_z);
|
||||
m_selection.translate(i.first, i.second, shift);
|
||||
m->translate_instance(i.second, shift);
|
||||
|
@ -5107,6 +5112,7 @@ void GLCanvas3D::_render_objects()
|
|||
m_volumes.set_z_range(-FLT_MAX, FLT_MAX);
|
||||
|
||||
m_volumes.set_clipping_plane(m_camera_clipping_plane.get_data());
|
||||
m_volumes.set_show_sinking_contours(! m_gizmos.is_hiding_instances());
|
||||
|
||||
GLShaderProgram* shader = wxGetApp().get_shader("gouraud");
|
||||
if (shader != nullptr) {
|
||||
|
|
|
@ -61,6 +61,8 @@ std::pair<bool, std::string> GLShadersManager::init()
|
|||
);
|
||||
// used to render variable layers heights in 3d editor
|
||||
valid &= append_shader("variable_layer_height", { "variable_layer_height.vs", "variable_layer_height.fs" });
|
||||
// used to render highlight contour around selected triangles inside the multi-material gizmo
|
||||
valid &= append_shader("mm_contour", { "mm_contour.vs", "mm_contour.fs" });
|
||||
|
||||
return { valid, error };
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
#include <string>
|
||||
|
||||
#include <boost/algorithm/string.hpp>
|
||||
#include <boost/algorithm/string/predicate.hpp>
|
||||
#include <boost/any.hpp>
|
||||
|
||||
#if __APPLE__
|
||||
|
@ -21,6 +22,7 @@
|
|||
|
||||
#include "AboutDialog.hpp"
|
||||
#include "MsgDialog.hpp"
|
||||
#include "format.hpp"
|
||||
|
||||
#include "libslic3r/Print.hpp"
|
||||
|
||||
|
@ -244,6 +246,127 @@ void warning_catcher(wxWindow* parent, const wxString& message)
|
|||
msg.ShowModal();
|
||||
}
|
||||
|
||||
static wxString bold(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>%s</b>", str);
|
||||
};
|
||||
|
||||
static wxString bold_string(const wxString& str)
|
||||
{
|
||||
return wxString::Format("<b>\"%s\"</b>", str);
|
||||
};
|
||||
|
||||
static void add_config_substitutions(const ConfigSubstitutions& conf_substitutions, wxString& changes)
|
||||
{
|
||||
changes += "<table>";
|
||||
for (const ConfigSubstitution& conf_substitution : conf_substitutions) {
|
||||
wxString new_val;
|
||||
const ConfigOptionDef* def = conf_substitution.opt_def;
|
||||
if (!def)
|
||||
continue;
|
||||
switch (def->type) {
|
||||
case coEnum:
|
||||
{
|
||||
const std::vector<std::string>& labels = def->enum_labels;
|
||||
const std::vector<std::string>& values = def->enum_values;
|
||||
int val = conf_substitution.new_value->getInt();
|
||||
|
||||
bool is_infill = def->opt_key == "top_fill_pattern" ||
|
||||
def->opt_key == "bottom_fill_pattern" ||
|
||||
def->opt_key == "fill_pattern";
|
||||
|
||||
// Each infill doesn't use all list of infill declared in PrintConfig.hpp.
|
||||
// So we should "convert" val to the correct one
|
||||
if (is_infill) {
|
||||
for (const auto& key_val : *def->enum_keys_map)
|
||||
if ((int)key_val.second == val) {
|
||||
auto it = std::find(values.begin(), values.end(), key_val.first);
|
||||
if (it == values.end())
|
||||
break;
|
||||
auto idx = it - values.begin();
|
||||
new_val = wxString("\"") + values[idx] + "\"" + " (" + from_u8(_utf8(labels[idx])) + ")";
|
||||
break;
|
||||
}
|
||||
if (new_val.IsEmpty()) {
|
||||
assert(false);
|
||||
new_val = _L("Undefined");
|
||||
}
|
||||
}
|
||||
else
|
||||
new_val = wxString("\"") + values[val] + "\"" + " (" + from_u8(_utf8(labels[val])) + ")";
|
||||
break;
|
||||
}
|
||||
case coBool:
|
||||
new_val = conf_substitution.new_value->getBool() ? "true" : "false";
|
||||
break;
|
||||
case coBools:
|
||||
if (conf_substitution.new_value->nullable())
|
||||
for (const char v : static_cast<const ConfigOptionBoolsNullable*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v == ConfigOptionBoolsNullable::nil_value() ? "nil" : v ? "true" : "false") + ", ";
|
||||
else
|
||||
for (const char v : static_cast<const ConfigOptionBools*>(conf_substitution.new_value.get())->values)
|
||||
new_val += std::string(v ? "true" : "false") + ", ";
|
||||
if (! new_val.empty())
|
||||
new_val.erase(new_val.begin() + new_val.size() - 2, new_val.end());
|
||||
break;
|
||||
default:
|
||||
assert(false);
|
||||
}
|
||||
|
||||
changes += format_wxstr("<tr><td><b>\"%1%\" (%2%)</b></td><td>: ", def->opt_key, _(def->label)) +
|
||||
format_wxstr(_L("%1% was substituted with %2%"), bold_string(conf_substitution.old_value), bold(new_val)) +
|
||||
"</td></tr>";
|
||||
}
|
||||
changes += "</table>";
|
||||
}
|
||||
|
||||
static wxString substitution_message(const wxString& changes)
|
||||
{
|
||||
return
|
||||
_L("Most likely the configuration was produced by a newer version of PrusaSlicer or by some PrusaSlicer fork.") + " " +
|
||||
_L("The following values were substituted:") + "\n" + changes + "\n\n" +
|
||||
_L("Review the substitutions and adjust them if needed.");
|
||||
}
|
||||
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions)
|
||||
{
|
||||
wxString changes;
|
||||
|
||||
auto preset_type_name = [](Preset::Type type) {
|
||||
switch (type) {
|
||||
case Preset::TYPE_PRINT: return _L("Print settings");
|
||||
case Preset::TYPE_SLA_PRINT: return _L("SLA print settings");
|
||||
case Preset::TYPE_FILAMENT: return _L("Filament");
|
||||
case Preset::TYPE_SLA_MATERIAL: return _L("SLA material");
|
||||
case Preset::TYPE_PRINTER: return _L("Printer");
|
||||
case Preset::TYPE_PHYSICAL_PRINTER: return _L("Physical Printer");
|
||||
default: assert(false); return wxString();
|
||||
}
|
||||
};
|
||||
|
||||
for (const PresetConfigSubstitutions& substitution : presets_config_substitutions) {
|
||||
changes += "\n\n" + format_wxstr("%1% : %2%", preset_type_name(substitution.preset_type), bold_string(substitution.preset_name));
|
||||
if (!substitution.preset_file.empty())
|
||||
changes += format_wxstr(" (%1%)", substitution.preset_file);
|
||||
|
||||
add_config_substitutions(substitution.substitutions, changes);
|
||||
}
|
||||
|
||||
InfoDialog msg(nullptr, _L("Configuration bundle was loaded, however some configuration values were not recognized."), substitution_message(changes));
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename)
|
||||
{
|
||||
wxString changes = "\n";
|
||||
add_config_substitutions(config_substitutions, changes);
|
||||
|
||||
InfoDialog msg(nullptr,
|
||||
format_wxstr(_L("Configuration file \"%1%\" was loaded, however some configuration values were not recognized."), from_u8(filename)),
|
||||
substitution_message(changes));
|
||||
msg.ShowModal();
|
||||
}
|
||||
|
||||
void create_combochecklist(wxComboCtrl* comboCtrl, const std::string& text, const std::string& items)
|
||||
{
|
||||
if (comboCtrl == nullptr)
|
||||
|
|
|
@ -7,6 +7,7 @@ namespace boost::filesystem { class path; }
|
|||
#include <wx/string.h>
|
||||
|
||||
#include "libslic3r/Config.hpp"
|
||||
#include "libslic3r/Preset.hpp"
|
||||
|
||||
class wxWindow;
|
||||
class wxMenuBar;
|
||||
|
@ -48,6 +49,8 @@ void show_info(wxWindow* parent, const wxString& message, const wxString& title
|
|||
void show_info(wxWindow* parent, const char* message, const char* title = nullptr);
|
||||
inline void show_info(wxWindow* parent, const std::string& message,const std::string& title = std::string()) { show_info(parent, message.c_str(), title.c_str()); }
|
||||
void warning_catcher(wxWindow* parent, const wxString& message);
|
||||
void show_substitutions_info(const PresetsConfigSubstitutions& presets_config_substitutions);
|
||||
void show_substitutions_info(const ConfigSubstitutions& config_substitutions, const std::string& filename);
|
||||
|
||||
// Creates a wxCheckListBoxComboPopup inside the given wxComboCtrl, filled with the given text and items.
|
||||
// Items data must be separated by '|', and contain the item name to be shown followed by its initial value (0 for false, 1 for true).
|
||||
|
|
|
@ -435,7 +435,9 @@ wxString file_wildcards(FileType file_type, const std::string &custom_extension)
|
|||
|
||||
/* FT_TEX */ "Texture (*.png, *.svg)|*.png;*.PNG;*.svg;*.SVG",
|
||||
|
||||
/* FT_PNGZIP */ "Masked SLA files (*.sl1)|*.sl1;*.SL1",
|
||||
/* FT_SL1 */ "Masked SLA files (*.sl1, *.sl1s)|*.sl1;*.SL1;*.sl1s;*.SL1S",
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
/* FT_SL1S */ "Masked SLA files (*.sl1s, *.sl1)|*.sl1s;*.SL1S;*.sl1;*.SL1",
|
||||
};
|
||||
|
||||
std::string out = defaults[file_type];
|
||||
|
@ -626,12 +628,8 @@ void GUI_App::post_init()
|
|||
this->plater()->load_gcode(wxString::FromUTF8(this->init_params->input_files[0].c_str()));
|
||||
}
|
||||
else {
|
||||
if (! this->init_params->preset_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! this->init_params->preset_substitutions.empty())
|
||||
show_substitutions_info(this->init_params->preset_substitutions);
|
||||
|
||||
#if 0
|
||||
// Load the cummulative config over the currently active profiles.
|
||||
|
@ -664,7 +662,7 @@ void GUI_App::post_init()
|
|||
|
||||
// show "Did you know" notification
|
||||
if (app_config->get("show_hints") == "1" && ! is_gcode_viewer())
|
||||
plater_->get_notification_manager()->push_hint_notification();
|
||||
plater_->get_notification_manager()->push_hint_notification(true);
|
||||
|
||||
// The extra CallAfter() is needed because of Mac, where this is the only way
|
||||
// to popup a modal dialog on start without screwing combo boxes.
|
||||
|
@ -916,7 +914,10 @@ bool GUI_App::on_init_inner()
|
|||
// Suppress the '- default -' presets.
|
||||
preset_bundle->set_default_suppressed(app_config->get("no_defaults") == "1");
|
||||
try {
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
// Enable all substitutions (in both user and system profiles), but log the substitutions in user profiles only.
|
||||
// If there are substitutions in system profiles, then a "reconfigure" event shall be triggered, which will force
|
||||
// installation of a compatible system preset, thus nullifying the system preset substitutions.
|
||||
init_params->preset_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSystemSilent);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(nullptr, ex.what());
|
||||
}
|
||||
|
@ -1861,9 +1862,10 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
child->SetFont(normal_font());
|
||||
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
app_config->set("on_snapshot",
|
||||
Slic3r::GUI::Config::SnapshotDB::singleton().take_snapshot(
|
||||
*app_config, Slic3r::GUI::Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data()).id);
|
||||
if (const Config::Snapshot *snapshot = Config::take_config_snapshot_report_error(
|
||||
*app_config, Config::Snapshot::SNAPSHOT_USER, dlg.GetValue().ToUTF8().data());
|
||||
snapshot != nullptr)
|
||||
app_config->set("on_snapshot", snapshot->id);
|
||||
}
|
||||
break;
|
||||
case ConfigMenuSnapshots:
|
||||
|
@ -1874,19 +1876,24 @@ void GUI_App::add_config_menu(wxMenuBar *menu)
|
|||
ConfigSnapshotDialog dlg(Slic3r::GUI::Config::SnapshotDB::singleton(), on_snapshot);
|
||||
dlg.ShowModal();
|
||||
if (!dlg.snapshot_to_activate().empty()) {
|
||||
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config))
|
||||
Config::SnapshotDB::singleton().take_snapshot(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK);
|
||||
if (! Config::SnapshotDB::singleton().is_on_snapshot(*app_config) &&
|
||||
! Config::take_config_snapshot_cancel_on_error(*app_config, Config::Snapshot::SNAPSHOT_BEFORE_ROLLBACK, "",
|
||||
GUI::format(_L("Continue to activate a configuration snapshot %1%?"), dlg.snapshot_to_activate())))
|
||||
break;
|
||||
try {
|
||||
app_config->set("on_snapshot", Config::SnapshotDB::singleton().restore_snapshot(dlg.snapshot_to_activate(), *app_config).id);
|
||||
// Enable substitutions, log both user and system substitutions. There should not be any substitutions performed when loading system
|
||||
// presets because compatibility of profiles shall be verified using the min_slic3r_version keys in config index, but users
|
||||
// are known to be creative and mess with the config files in various ways.
|
||||
if (PresetsConfigSubstitutions all_substitutions = preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
! all_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
! all_substitutions.empty())
|
||||
show_substitutions_info(all_substitutions);
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
load_current_presets();
|
||||
|
||||
// update config wizard in respect to the new config
|
||||
update_wizard_from_config();
|
||||
} catch (std::exception &ex) {
|
||||
GUI::show_error(nullptr, _L("Failed to activate configuration snapshot.") + "\n" + into_u8(ex.what()));
|
||||
}
|
||||
|
@ -2059,7 +2066,7 @@ std::vector<std::pair<unsigned int, std::string>> GUI_App::get_selected_presets(
|
|||
// to notify the user whether he is aware that some preset changes will be lost.
|
||||
bool GUI_App::check_and_save_current_preset_changes(const wxString& header)
|
||||
{
|
||||
if (this->plater()->model().objects.empty() && has_current_preset_changes()) {
|
||||
if (/*this->plater()->model().objects.empty() && */has_current_preset_changes()) {
|
||||
UnsavedChangesDialog dlg(header);
|
||||
if (wxGetApp().app_config->get("default_action_on_close_application") == "none" && dlg.ShowModal() == wxID_CANCEL)
|
||||
return false;
|
||||
|
@ -2138,6 +2145,17 @@ void GUI_App::load_current_presets(bool check_printer_presets_ /*= true*/)
|
|||
}
|
||||
}
|
||||
|
||||
void GUI_App::update_wizard_from_config()
|
||||
{
|
||||
if (!m_wizard)
|
||||
return;
|
||||
// If ConfigWizard was created before changing of the configuration,
|
||||
// we have to destroy it to have possibility to create it again in respect to the new config's parameters
|
||||
m_wizard->Reparent(nullptr);
|
||||
m_wizard->Destroy();
|
||||
m_wizard = nullptr;
|
||||
}
|
||||
|
||||
bool GUI_App::OnExceptionInMainLoop()
|
||||
{
|
||||
generic_exception_handle();
|
||||
|
@ -2298,7 +2316,13 @@ bool GUI_App::run_wizard(ConfigWizard::RunReason reason, ConfigWizard::StartPage
|
|||
{
|
||||
wxCHECK_MSG(mainframe != nullptr, false, "Internal error: Main frame not created / null");
|
||||
|
||||
if (reason == ConfigWizard::RR_USER)
|
||||
if (PresetUpdater::UpdateResult result = preset_updater->config_update(app_config->orig_version(), PresetUpdater::UpdateParams::FORCED_BEFORE_WIZARD);
|
||||
result == PresetUpdater::R_ALL_CANCELED)
|
||||
return false;
|
||||
|
||||
if (! m_wizard) {
|
||||
wxBusyCursor wait;
|
||||
m_wizard = new ConfigWizard(mainframe);
|
||||
}
|
||||
|
||||
|
@ -2466,7 +2490,7 @@ void GUI_App::check_updates(const bool verbose)
|
|||
{
|
||||
PresetUpdater::UpdateResult updater_result;
|
||||
try {
|
||||
updater_result = preset_updater->config_update(app_config->orig_version(), verbose);
|
||||
updater_result = preset_updater->config_update(app_config->orig_version(), verbose ? PresetUpdater::UpdateParams::SHOW_TEXT_BOX : PresetUpdater::UpdateParams::SHOW_NOTIFICATION);
|
||||
if (updater_result == PresetUpdater::R_INCOMPAT_EXIT) {
|
||||
mainframe->Close();
|
||||
}
|
||||
|
|
|
@ -66,7 +66,9 @@ enum FileType
|
|||
|
||||
FT_TEX,
|
||||
|
||||
FT_PNGZIP,
|
||||
FT_SL1,
|
||||
// Workaround for OSX file picker, for some reason it always saves with the 1st extension.
|
||||
FT_SL1S,
|
||||
|
||||
FT_SIZE,
|
||||
};
|
||||
|
@ -247,6 +249,7 @@ public:
|
|||
bool check_print_host_queue();
|
||||
bool checked_tab(Tab* tab);
|
||||
void load_current_presets(bool check_printer_presets = true);
|
||||
void update_wizard_from_config();
|
||||
|
||||
wxString current_language_code() const { return m_wxLocale->GetCanonicalName(); }
|
||||
// Translate the language code to a code, for which Prusa Research maintains translations. Defaults to "en_US".
|
||||
|
|
|
@ -145,7 +145,7 @@ wxSizer* ObjectLayers::create_layer(const t_layer_height_range& range, PlusMinus
|
|||
auto temp = new wxStaticText(m_parent, wxID_ANY, _L("mm"));
|
||||
temp->SetBackgroundStyle(wxBG_STYLE_PAINT);
|
||||
temp->SetFont(wxGetApp().normal_font());
|
||||
sizer->Add(temp, 0, wxLEFT, wxGetApp().em_unit());
|
||||
sizer->Add(temp, 0, wxLEFT | wxALIGN_CENTER_VERTICAL, wxGetApp().em_unit());
|
||||
|
||||
m_grid_sizer->Add(sizer);
|
||||
|
||||
|
|
|
@ -2517,7 +2517,7 @@ wxDataViewItem ObjectList::add_settings_item(wxDataViewItem parent_item, const D
|
|||
}
|
||||
|
||||
|
||||
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/)
|
||||
void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selections/* = nullptr*/, bool added_object/* = false*/)
|
||||
{
|
||||
const ModelObject* model_object = (*m_objects)[obj_idx];
|
||||
wxDataViewItem item_obj = m_objects_model->GetItemById(obj_idx);
|
||||
|
@ -2561,8 +2561,8 @@ void ObjectList::update_info_items(size_t obj_idx, wxDataViewItemArray* selectio
|
|||
if (! shows && should_show) {
|
||||
m_objects_model->AddInfoChild(item_obj, type);
|
||||
Expand(item_obj);
|
||||
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
|
||||
|
||||
if (added_object)
|
||||
wxGetApp().notification_manager()->push_updated_item_info_notification(type);
|
||||
}
|
||||
else if (shows && ! should_show) {
|
||||
if (!selections)
|
||||
|
@ -2594,7 +2594,7 @@ void ObjectList::add_object_to_list(size_t obj_idx, bool call_selection_changed)
|
|||
model_object->config.has("extruder") ? model_object->config.extruder() : 0,
|
||||
get_mesh_errors_count(obj_idx) > 0);
|
||||
|
||||
update_info_items(obj_idx);
|
||||
update_info_items(obj_idx, nullptr, true);
|
||||
|
||||
// add volumes to the object
|
||||
if (model_object->volumes.size() > 1) {
|
||||
|
@ -3857,6 +3857,7 @@ void ObjectList::instances_to_separated_object(const int obj_idx, const std::set
|
|||
|
||||
// update printable state for new volumes on canvas3D
|
||||
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_object(new_obj_indx);
|
||||
update_info_items(new_obj_indx);
|
||||
}
|
||||
|
||||
void ObjectList::instances_to_separated_objects(const int obj_idx)
|
||||
|
@ -3889,6 +3890,8 @@ void ObjectList::instances_to_separated_objects(const int obj_idx)
|
|||
|
||||
// update printable state for new volumes on canvas3D
|
||||
wxGetApp().plater()->canvas3D()->update_instance_printable_state_for_objects(object_idxs);
|
||||
for (size_t object : object_idxs)
|
||||
update_info_items(object);
|
||||
}
|
||||
|
||||
void ObjectList::split_instances()
|
||||
|
@ -3953,6 +3956,7 @@ void ObjectList::fix_through_netfabb()
|
|||
wxGetApp().plater()->fix_through_netfabb(obj_idx, vol_idx);
|
||||
|
||||
update_item_error_icon(obj_idx, vol_idx);
|
||||
update_info_items(obj_idx);
|
||||
}
|
||||
|
||||
void ObjectList::simplify()
|
||||
|
@ -4248,5 +4252,10 @@ ModelObject* ObjectList::object(const int obj_idx) const
|
|||
return (*m_objects)[obj_idx];
|
||||
}
|
||||
|
||||
bool ObjectList::has_paint_on_segmentation()
|
||||
{
|
||||
return m_objects_model->HasInfoItem(InfoItemType::MmuSegmentation);
|
||||
}
|
||||
|
||||
} //namespace GUI
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -350,7 +350,7 @@ public:
|
|||
void update_and_show_object_settings_item();
|
||||
void update_settings_item_and_selection(wxDataViewItem item, wxDataViewItemArray& selections);
|
||||
void update_object_list_by_printer_technology();
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr);
|
||||
void update_info_items(size_t obj_idx, wxDataViewItemArray* selections = nullptr, bool added_object = false);
|
||||
|
||||
void instances_to_separated_object(const int obj_idx, const std::set<int>& inst_idx);
|
||||
void instances_to_separated_objects(const int obj_idx);
|
||||
|
@ -379,6 +379,7 @@ public:
|
|||
void set_extruder_for_selected_items(const int extruder) const ;
|
||||
wxDataViewItemArray reorder_volumes_and_get_selection(int obj_idx, std::function<bool(const ModelVolume*)> add_to_selection = nullptr);
|
||||
void apply_volumes_order();
|
||||
bool has_paint_on_segmentation();
|
||||
|
||||
private:
|
||||
#ifdef __WXOSX__
|
||||
|
|
|
@ -709,9 +709,9 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
|||
const ExPolygons& bottom = object->get_layer(0)->lslices;
|
||||
double bottom_area = area(bottom);
|
||||
|
||||
// at least 30% of object's height have to be a solid
|
||||
int i;
|
||||
for (i = 1; i < int(0.3 * num_layers); ++ i) {
|
||||
// at least 25% of object's height have to be a solid
|
||||
int i, min_solid_height = int(0.25 * num_layers);
|
||||
for (i = 1; i <= min_solid_height; ++ i) {
|
||||
double cur_area = area(object->get_layer(i)->lslices);
|
||||
if (cur_area != bottom_area && fabs(cur_area - bottom_area) > scale_(scale_(1))) {
|
||||
// but due to the elephant foot compensation, the first layer may be slightly smaller than the others
|
||||
|
@ -723,7 +723,7 @@ void Preview::update_layers_slider(const std::vector<double>& layers_z, bool kee
|
|||
break;
|
||||
}
|
||||
}
|
||||
if (i < int(0.3 * num_layers))
|
||||
if (i < min_solid_height)
|
||||
continue;
|
||||
|
||||
// bottom layer have to be a biggest, so control relation between bottom layer and object size
|
||||
|
@ -788,11 +788,11 @@ void Preview::update_layers_slider_mode()
|
|||
object->config.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
|
||||
if (object->volumes.size() > 1)
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if (volume->config.has("extruder") &&
|
||||
volume->config.option("extruder")->getInt() != extruder)
|
||||
return false;
|
||||
for (ModelVolume* volume : object->volumes)
|
||||
if (volume->config.has("extruder") &&
|
||||
volume->config.option("extruder")->getInt() != extruder ||
|
||||
!volume->mmu_segmentation_facets.empty())
|
||||
return false;
|
||||
|
||||
for (const auto& range : object->layer_config_ranges)
|
||||
if (range.second.has("extruder") &&
|
||||
|
|
|
@ -67,8 +67,8 @@ void GLGizmoFdmSupports::render_painter_gizmo() const
|
|||
glsafe(::glEnable(GL_DEPTH_TEST));
|
||||
|
||||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
|
|
@ -42,6 +42,8 @@ std::string GLGizmoFlatten::on_get_name() const
|
|||
|
||||
bool GLGizmoFlatten::on_is_activable() const
|
||||
{
|
||||
// This is assumed in GLCanvas3D::do_rotate, do not change this
|
||||
// without updating that function too.
|
||||
return m_parent.get_selection().is_single_full_instance();
|
||||
}
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ void GLGizmoMmuSegmentation::on_shutdown()
|
|||
std::string GLGizmoMmuSegmentation::on_get_name() const
|
||||
{
|
||||
// FIXME Lukas H.: Discuss and change shortcut
|
||||
return (_L("MMU painting") + " [N]").ToUTF8().data();
|
||||
return (_L("Multimaterial painting") + " [N]").ToUTF8().data();
|
||||
}
|
||||
|
||||
bool GLGizmoMmuSegmentation::on_is_selectable() const
|
||||
|
@ -147,6 +147,7 @@ void GLGizmoMmuSegmentation::render_painter_gizmo() const
|
|||
render_triangles(selection, false);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
@ -327,8 +328,13 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
ImGui::AlignTextToFramePadding();
|
||||
ImGui::SameLine(tool_type_offset + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_brush);
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH))
|
||||
if (m_imgui->radio_button(m_desc["tool_brush"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BRUSH)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BRUSH;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
|
@ -339,24 +345,6 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_bucket_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_seed_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_seed_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::SEED_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::SEED_FILL;
|
||||
|
@ -374,6 +362,24 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::SameLine(tool_type_offset + tool_type_radio_brush + tool_type_radio_seed_fill + m_imgui->scaled(0.f));
|
||||
ImGui::PushItemWidth(tool_type_radio_bucket_fill);
|
||||
if (m_imgui->radio_button(m_desc["tool_bucket_fill"], m_tool_type == GLGizmoMmuSegmentation::ToolType::BUCKET_FILL)) {
|
||||
m_tool_type = GLGizmoMmuSegmentation::ToolType::BUCKET_FILL;
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
ImGui::TextUnformatted(_L("Paints neighboring facets that have the same color.").ToUTF8().data());
|
||||
ImGui::PopTextWrapPos();
|
||||
ImGui::EndTooltip();
|
||||
}
|
||||
|
||||
ImGui::Separator();
|
||||
|
||||
if(m_tool_type == ToolType::BRUSH) {
|
||||
|
@ -459,7 +465,12 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
|
|||
"placed after the number with no whitespace in between.");
|
||||
ImGui::SameLine(sliders_width);
|
||||
ImGui::PushItemWidth(window_width - sliders_width);
|
||||
m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data());
|
||||
if(m_imgui->slider_float("##seed_fill_angle", &m_seed_fill_angle, SeedFillAngleMin, SeedFillAngleMax, format_str.data()))
|
||||
for (auto &triangle_selector : m_triangle_selectors) {
|
||||
triangle_selector->seed_fill_unselect_all_triangles();
|
||||
triangle_selector->request_update_render_data();
|
||||
}
|
||||
|
||||
if (ImGui::IsItemHovered()) {
|
||||
ImGui::BeginTooltip();
|
||||
ImGui::PushTextWrapPos(max_tooltip_width);
|
||||
|
@ -586,10 +597,13 @@ std::array<float, 4> GLGizmoMmuSegmentation::get_cursor_sphere_right_button_colo
|
|||
return {color[0], color[1], color[2], 0.25f};
|
||||
}
|
||||
|
||||
static std::array<float, 4> get_seed_fill_color(const std::array<float, 4> &base_color)
|
||||
{
|
||||
return {base_color[0] * 0.75f, base_color[1] * 0.75f, base_color[2] * 0.75f, 1.f};
|
||||
}
|
||||
|
||||
void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
||||
{
|
||||
static constexpr std::array<float, 4> seed_fill_color{0.f, 1.f, 0.44f, 1.f};
|
||||
|
||||
if (m_update_render_data)
|
||||
update_render_data();
|
||||
|
||||
|
@ -602,12 +616,24 @@ void TriangleSelectorMmGui::render(ImGuiWrapper *imgui)
|
|||
|
||||
for (size_t color_idx = 0; color_idx < m_gizmo_scene.triangle_indices.size(); ++color_idx)
|
||||
if (m_gizmo_scene.has_VBOs(color_idx)) {
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color :
|
||||
color_idx == (m_gizmo_scene.triangle_indices.size() - 1) ? seed_fill_color :
|
||||
m_colors[color_idx - 1]);
|
||||
if (color_idx > m_colors.size()) // Seed fill VBO
|
||||
shader->set_uniform("uniform_color", get_seed_fill_color(color_idx == (m_colors.size() + 1) ? m_default_volume_color : m_colors[color_idx - (m_colors.size() + 1) - 1]));
|
||||
else // Normal VBO
|
||||
shader->set_uniform("uniform_color", color_idx == 0 ? m_default_volume_color : m_colors[color_idx - 1]);
|
||||
|
||||
m_gizmo_scene.render(color_idx);
|
||||
}
|
||||
|
||||
if (m_gizmo_scene.has_contour_VBO()) {
|
||||
ScopeGuard guard_gouraud([shader]() { shader->start_using(); });
|
||||
shader->stop_using();
|
||||
|
||||
auto *contour_shader = wxGetApp().get_shader("mm_contour");
|
||||
contour_shader->start_using();
|
||||
m_gizmo_scene.render_contour();
|
||||
contour_shader->stop_using();
|
||||
}
|
||||
|
||||
m_update_render_data = false;
|
||||
}
|
||||
|
||||
|
@ -624,10 +650,10 @@ void TriangleSelectorMmGui::update_render_data()
|
|||
|
||||
for (const Triangle &tr : m_triangles)
|
||||
if (tr.valid() && !tr.is_split()) {
|
||||
int color = int(tr.get_state());
|
||||
std::vector<int> &iva = tr.is_selected_by_seed_fill() ? m_gizmo_scene.triangle_indices.back() :
|
||||
color < int(m_gizmo_scene.triangle_indices.size() - 1) ? m_gizmo_scene.triangle_indices[color] :
|
||||
m_gizmo_scene.triangle_indices.front();
|
||||
int color = int(tr.get_state()) <= int(m_colors.size()) ? int(tr.get_state()) : 0;
|
||||
assert(m_colors.size() + 1 + color < m_gizmo_scene.triangle_indices.size());
|
||||
std::vector<int> &iva = m_gizmo_scene.triangle_indices[color + tr.is_selected_by_seed_fill() * (m_colors.size() + 1)];
|
||||
|
||||
if (iva.size() + 3 > iva.capacity())
|
||||
iva.reserve(next_highest_power_of_2(iva.size() + 3));
|
||||
|
||||
|
@ -640,6 +666,24 @@ void TriangleSelectorMmGui::update_render_data()
|
|||
m_gizmo_scene.triangle_indices_sizes[color_idx] = m_gizmo_scene.triangle_indices[color_idx].size();
|
||||
|
||||
m_gizmo_scene.finalize_triangle_indices();
|
||||
|
||||
std::vector<Vec2i> contour_edges = this->get_seed_fill_contour();
|
||||
m_gizmo_scene.contour_vertices.reserve(contour_edges.size() * 6);
|
||||
for (const Vec2i &edge : contour_edges) {
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.x());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.y());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(0)].v.z());
|
||||
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.x());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.y());
|
||||
m_gizmo_scene.contour_vertices.emplace_back(m_vertices[edge(1)].v.z());
|
||||
}
|
||||
|
||||
m_gizmo_scene.contour_indices.assign(m_gizmo_scene.contour_vertices.size() / 3, 0);
|
||||
std::iota(m_gizmo_scene.contour_indices.begin(), m_gizmo_scene.contour_indices.end(), 0);
|
||||
m_gizmo_scene.contour_indices_size = m_gizmo_scene.contour_indices.size();
|
||||
|
||||
m_gizmo_scene.finalize_contour();
|
||||
}
|
||||
|
||||
wxString GLGizmoMmuSegmentation::handle_snapshot_action_name(bool shift_down, GLGizmoPainterBase::Button button_down) const
|
||||
|
@ -663,6 +707,14 @@ void GLMmSegmentationGizmo3DScene::release_geometry() {
|
|||
glsafe(::glDeleteBuffers(1, &triangle_indices_VBO_id));
|
||||
triangle_indices_VBO_id = 0;
|
||||
}
|
||||
if (this->contour_vertices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->contour_vertices_VBO_id));
|
||||
this->contour_vertices_VBO_id = 0;
|
||||
}
|
||||
if (this->contour_indices_VBO_id) {
|
||||
glsafe(::glDeleteBuffers(1, &this->contour_indices_VBO_id));
|
||||
this->contour_indices_VBO_id = 0;
|
||||
}
|
||||
this->clear();
|
||||
}
|
||||
|
||||
|
@ -673,6 +725,10 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
|||
assert(this->vertices_VBO_id != 0);
|
||||
assert(this->triangle_indices_VBO_ids[triangle_indices_idx] != 0);
|
||||
|
||||
ScopeGuard offset_fill_guard([]() { glsafe(::glDisable(GL_POLYGON_OFFSET_FILL)); });
|
||||
glsafe(::glEnable(GL_POLYGON_OFFSET_FILL));
|
||||
glsafe(::glPolygonOffset(5.0, 5.0));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), (const void*)(0 * sizeof(float))));
|
||||
|
||||
|
@ -690,13 +746,36 @@ void GLMmSegmentationGizmo3DScene::render(size_t triangle_indices_idx) const
|
|||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::render_contour() const
|
||||
{
|
||||
assert(this->contour_vertices_VBO_id != 0);
|
||||
assert(this->contour_indices_VBO_id != 0);
|
||||
|
||||
glsafe(::glLineWidth(4.0f));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id));
|
||||
glsafe(::glVertexPointer(3, GL_FLOAT, 3 * sizeof(float), nullptr));
|
||||
|
||||
glsafe(::glEnableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
if (this->contour_indices_size > 0) {
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->contour_indices_VBO_id));
|
||||
glsafe(::glDrawElements(GL_LINES, GLsizei(this->contour_indices_size), GL_UNSIGNED_INT, nullptr));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
glsafe(::glDisableClientState(GL_VERTEX_ARRAY));
|
||||
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_vertices()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
if (!this->vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * 4, this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->vertices.size() * sizeof(float), this->vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->vertices.clear();
|
||||
}
|
||||
|
@ -711,19 +790,32 @@ void GLMmSegmentationGizmo3DScene::finalize_triangle_indices()
|
|||
if (!this->triangle_indices[buffer_idx].empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices_VBO_ids[buffer_idx]));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * 4, this->triangle_indices[buffer_idx].data(),
|
||||
GL_STATIC_DRAW));
|
||||
glsafe(::glBufferData(GL_ELEMENT_ARRAY_BUFFER, this->triangle_indices[buffer_idx].size() * sizeof(int), this->triangle_indices[buffer_idx].data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, 0));
|
||||
this->triangle_indices[buffer_idx].clear();
|
||||
}
|
||||
}
|
||||
|
||||
void GLMmSegmentationGizmo3DScene::finalize_geometry()
|
||||
void GLMmSegmentationGizmo3DScene::finalize_contour()
|
||||
{
|
||||
assert(this->vertices_VBO_id == 0);
|
||||
assert(this->triangle_indices.size() == this->triangle_indices_VBO_ids.size());
|
||||
finalize_vertices();
|
||||
finalize_triangle_indices();
|
||||
assert(this->contour_vertices_VBO_id == 0);
|
||||
assert(this->contour_indices_VBO_id == 0);
|
||||
|
||||
if (!this->contour_vertices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->contour_vertices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_vertices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_vertices.size() * sizeof(float), this->contour_vertices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->contour_vertices.clear();
|
||||
}
|
||||
|
||||
if (!this->contour_indices.empty()) {
|
||||
glsafe(::glGenBuffers(1, &this->contour_indices_VBO_id));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, this->contour_indices_VBO_id));
|
||||
glsafe(::glBufferData(GL_ARRAY_BUFFER, this->contour_indices.size() * sizeof(unsigned int), this->contour_indices.data(), GL_STATIC_DRAW));
|
||||
glsafe(::glBindBuffer(GL_ARRAY_BUFFER, 0));
|
||||
this->contour_indices.clear();
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace Slic3r
|
||||
|
|
|
@ -25,9 +25,8 @@ public:
|
|||
return this->triangle_indices_VBO_ids[triangle_indices_idx] != 0;
|
||||
}
|
||||
|
||||
// Finalize the initialization of the geometry and indices, upload the geometry and indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_geometry();
|
||||
[[nodiscard]] inline bool has_contour_VBO() const { return this->contour_indices_VBO_id != 0; }
|
||||
|
||||
// Release the geometry data, release OpenGL VBOs.
|
||||
void release_geometry();
|
||||
// Finalize the initialization of the geometry, upload the geometry to OpenGL VBO objects
|
||||
|
@ -36,6 +35,9 @@ public:
|
|||
// Finalize the initialization of the indices, upload the indices to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_triangle_indices();
|
||||
// Finalize the initialization of the contour geometry and the indices, upload both to OpenGL VBO objects
|
||||
// and possibly releasing it if it has been loaded into the VBOs.
|
||||
void finalize_contour();
|
||||
|
||||
void clear()
|
||||
{
|
||||
|
@ -45,28 +47,41 @@ public:
|
|||
|
||||
for (size_t &triangle_indices_size : this->triangle_indices_sizes)
|
||||
triangle_indices_size = 0;
|
||||
|
||||
this->contour_vertices.clear();
|
||||
this->contour_indices.clear();
|
||||
this->contour_indices_size = 0;
|
||||
}
|
||||
|
||||
void render(size_t triangle_indices_idx) const;
|
||||
|
||||
void render_contour() const;
|
||||
|
||||
std::vector<float> vertices;
|
||||
std::vector<std::vector<int>> triangle_indices;
|
||||
|
||||
std::vector<float> contour_vertices;
|
||||
std::vector<int> contour_indices;
|
||||
|
||||
// When the triangle indices are loaded into the graphics card as Vertex Buffer Objects,
|
||||
// the above mentioned std::vectors are cleared and the following variables keep their original length.
|
||||
std::vector<size_t> triangle_indices_sizes;
|
||||
size_t contour_indices_size{0};
|
||||
|
||||
// IDs of the Vertex Array Objects, into which the geometry has been loaded.
|
||||
// Zero if the VBOs are not sent to GPU yet.
|
||||
unsigned int vertices_VBO_id{0};
|
||||
std::vector<unsigned int> triangle_indices_VBO_ids;
|
||||
|
||||
unsigned int contour_vertices_VBO_id{0};
|
||||
unsigned int contour_indices_VBO_id{0};
|
||||
};
|
||||
|
||||
class TriangleSelectorMmGui : public TriangleSelectorGUI {
|
||||
public:
|
||||
// Plus 2 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the last position is allocated for seed fill.
|
||||
// Plus 1 in the initialization of m_gizmo_scene is because the first position is allocated for non-painted triangles, and the indices above colors.size() are allocated for seed fill.
|
||||
explicit TriangleSelectorMmGui(const TriangleMesh &mesh, const std::vector<std::array<float, 4>> &colors, const std::array<float, 4> &default_volume_color)
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(colors.size() + 2) {}
|
||||
: TriangleSelectorGUI(mesh), m_colors(colors), m_default_volume_color(default_volume_color), m_gizmo_scene(2 * (colors.size() + 1)) {}
|
||||
~TriangleSelectorMmGui() override = default;
|
||||
|
||||
// Render current selection. Transformation matrices are supposed
|
||||
|
|
|
@ -283,7 +283,7 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
return true;
|
||||
}
|
||||
else if (alt_down) {
|
||||
if (m_tool_type == ToolType::BRUSH) {
|
||||
if (m_tool_type == ToolType::BRUSH && (m_cursor_type == TriangleSelector::CursorType::SPHERE || m_cursor_type == TriangleSelector::CursorType::CIRCLE)) {
|
||||
m_cursor_radius = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_cursor_radius - CursorRadiusStep, CursorRadiusMin)
|
||||
: std::min(m_cursor_radius + CursorRadiusStep, CursorRadiusMax);
|
||||
m_parent.set_as_dirty();
|
||||
|
@ -292,6 +292,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
m_seed_fill_angle = action == SLAGizmoEventType::MouseWheelDown ? std::max(m_seed_fill_angle - SeedFillAngleStep, SeedFillAngleMin)
|
||||
: std::min(m_seed_fill_angle + SeedFillAngleStep, SeedFillAngleMax);
|
||||
m_parent.set_as_dirty();
|
||||
if (m_rr.mesh_id != -1) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true);
|
||||
m_triangle_selectors[m_rr.mesh_id]->request_update_render_data();
|
||||
m_seed_fill_last_mesh_id = m_rr.mesh_id;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
|
@ -319,11 +324,11 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
new_state = action == SLAGizmoEventType::LeftDown ? this->get_left_button_state_type() : this->get_right_button_state_type();
|
||||
}
|
||||
|
||||
const Camera& camera = wxGetApp().plater()->get_camera();
|
||||
const Selection& selection = m_parent.get_selection();
|
||||
const ModelObject* mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance* mi = mo->instances[selection.get_instance_idx()];
|
||||
const Transform3d& instance_trafo = mi->get_transformation().get_matrix();
|
||||
const Camera &camera = wxGetApp().plater()->get_camera();
|
||||
const Selection &selection = m_parent.get_selection();
|
||||
const ModelObject *mo = m_c->selection_info()->model_object();
|
||||
const ModelInstance *mi = mo->instances[selection.get_instance_idx()];
|
||||
const Transform3d &instance_trafo = mi->get_transformation().get_matrix();
|
||||
|
||||
// List of mouse positions that will be used as seeds for painting.
|
||||
std::vector<Vec2d> mouse_positions{mouse_position};
|
||||
|
@ -382,6 +387,13 @@ bool GLGizmoPainterBase::gizmo_event(SLAGizmoEventType action, const Vec2d& mous
|
|||
assert(m_rr.mesh_id < int(m_triangle_selectors.size()));
|
||||
if (m_tool_type == ToolType::SEED_FILL || m_tool_type == ToolType::BUCKET_FILL || (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)) {
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_apply_on_triangles(new_state);
|
||||
if (m_tool_type == ToolType::SEED_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->seed_fill_select_triangles(m_rr.hit, int(m_rr.facet), m_seed_fill_angle, true);
|
||||
else if (m_tool_type == ToolType::BRUSH && m_cursor_type == TriangleSelector::CursorType::POINTER)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), false, true);
|
||||
else if (m_tool_type == ToolType::BUCKET_FILL)
|
||||
m_triangle_selectors[m_rr.mesh_id]->bucket_fill_select_triangles(m_rr.hit, int(m_rr.facet), true, true);
|
||||
|
||||
m_seed_fill_last_mesh_id = -1;
|
||||
} else if (m_tool_type == ToolType::BRUSH)
|
||||
m_triangle_selectors[m_rr.mesh_id]->select_patch(m_rr.hit, int(m_rr.facet), camera_pos, m_cursor_radius, m_cursor_type,
|
||||
|
|
|
@ -114,7 +114,7 @@ protected:
|
|||
|
||||
bool m_triangle_splitting_enabled = true;
|
||||
ToolType m_tool_type = ToolType::BRUSH;
|
||||
float m_seed_fill_angle = 0.f;
|
||||
float m_seed_fill_angle = 30.f;
|
||||
|
||||
static constexpr float SeedFillAngleMin = 0.0f;
|
||||
static constexpr float SeedFillAngleMax = 90.f;
|
||||
|
|
|
@ -63,6 +63,7 @@ void GLGizmoSeam::render_painter_gizmo() const
|
|||
render_triangles(selection);
|
||||
|
||||
m_c->object_clipper()->render_cut();
|
||||
m_c->instances_hider()->render_cut();
|
||||
render_cursor();
|
||||
|
||||
glsafe(::glDisable(GL_BLEND));
|
||||
|
|
|
@ -906,10 +906,11 @@ void GLGizmoSlaSupports::on_set_state()
|
|||
// on OSX with the wxMessageDialog being shown several times when clicked into.
|
||||
//wxMessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
MessageDialog dlg(GUI::wxGetApp().mainframe, _L("Do you want to save your manually "
|
||||
"edited support points?") + "\n",_L("Save changes?"), wxICON_QUESTION | wxYES | wxNO);
|
||||
if (dlg.ShowModal() == wxID_YES)
|
||||
"edited support points?") + "\n",_L("Save support points?"), wxICON_QUESTION | wxYES | wxNO | wxCANCEL );
|
||||
int ret = dlg.ShowModal();
|
||||
if (ret == wxID_YES)
|
||||
editing_mode_apply_changes();
|
||||
else
|
||||
else if (ret == wxID_NO)
|
||||
editing_mode_discard_changes();
|
||||
});
|
||||
// refuse to be turned off so the gizmo is active when the CallAfter is executed
|
||||
|
|
|
@ -150,6 +150,30 @@ void InstancesHider::on_update()
|
|||
canvas->toggle_model_objects_visibility(false);
|
||||
canvas->toggle_model_objects_visibility(true, mo, active_inst);
|
||||
canvas->toggle_sla_auxiliaries_visibility(m_show_supports, mo, active_inst);
|
||||
canvas->set_use_clipping_planes(true);
|
||||
// Some objects may be sinking, do not show whatever is below the bed.
|
||||
canvas->set_clipping_plane(0, ClippingPlane(Vec3d::UnitZ(), 0.));
|
||||
canvas->set_clipping_plane(1, ClippingPlane(-Vec3d::UnitZ(), std::numeric_limits<double>::max()));
|
||||
|
||||
|
||||
std::vector<const TriangleMesh*> meshes;
|
||||
for (const ModelVolume* mv : mo->volumes)
|
||||
meshes.push_back(&mv->mesh());
|
||||
|
||||
if (meshes != m_old_meshes) {
|
||||
m_clippers.clear();
|
||||
for (const TriangleMesh* mesh : meshes) {
|
||||
m_clippers.emplace_back(new MeshClipper);
|
||||
if (mo->get_instance_min_z(active_inst) < SINKING_Z_THRESHOLD)
|
||||
m_clippers.back()->set_plane(ClippingPlane(-Vec3d::UnitZ(), 0.));
|
||||
else {
|
||||
m_clippers.back()->set_plane(ClippingPlane::ClipsNothing());
|
||||
m_clippers.back()->set_limiting_plane(ClippingPlane::ClipsNothing());
|
||||
}
|
||||
m_clippers.back()->set_mesh(*mesh);
|
||||
}
|
||||
m_old_meshes = meshes;
|
||||
}
|
||||
}
|
||||
else
|
||||
canvas->toggle_model_objects_visibility(true);
|
||||
|
@ -158,6 +182,9 @@ void InstancesHider::on_update()
|
|||
void InstancesHider::on_release()
|
||||
{
|
||||
get_pool()->get_canvas()->toggle_model_objects_visibility(true);
|
||||
get_pool()->get_canvas()->set_use_clipping_planes(false);
|
||||
m_old_meshes.clear();
|
||||
m_clippers.clear();
|
||||
}
|
||||
|
||||
void InstancesHider::show_supports(bool show) {
|
||||
|
@ -167,6 +194,38 @@ void InstancesHider::show_supports(bool show) {
|
|||
}
|
||||
}
|
||||
|
||||
void InstancesHider::render_cut() const
|
||||
{
|
||||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
Geometry::Transformation vol_trafo = mv->get_transformation();
|
||||
Geometry::Transformation trafo = inst_trafo * vol_trafo;
|
||||
trafo.set_offset(trafo.get_offset() + Vec3d(0., 0., sel_info->get_sla_shift()));
|
||||
|
||||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_transformation(trafo);
|
||||
const ObjectClipper* obj_clipper = get_pool()->object_clipper();
|
||||
if (obj_clipper->is_valid() && obj_clipper->get_clipping_plane()
|
||||
&& obj_clipper->get_position() != 0.) {
|
||||
ClippingPlane clp = *get_pool()->object_clipper()->get_clipping_plane();
|
||||
clp.set_normal(-clp.get_normal());
|
||||
clipper->set_limiting_plane(clp);
|
||||
} else
|
||||
clipper->set_limiting_plane(ClippingPlane::ClipsNothing());
|
||||
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(0.8f, 0.3f, 0.0f));
|
||||
clipper->render_cut();
|
||||
glsafe(::glPopMatrix());
|
||||
|
||||
++clipper_id;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void HollowedMesh::on_update()
|
||||
|
@ -348,6 +407,7 @@ void ObjectClipper::render_cut() const
|
|||
const SelectionInfo* sel_info = get_pool()->selection_info();
|
||||
const ModelObject* mo = sel_info->model_object();
|
||||
Geometry::Transformation inst_trafo = mo->instances[sel_info->get_active_instance()]->get_transformation();
|
||||
const bool sinking = mo->bounding_box().min.z() < SINKING_Z_THRESHOLD;
|
||||
|
||||
size_t clipper_id = 0;
|
||||
for (const ModelVolume* mv : mo->volumes) {
|
||||
|
@ -358,7 +418,9 @@ void ObjectClipper::render_cut() const
|
|||
auto& clipper = m_clippers[clipper_id];
|
||||
clipper->set_plane(*m_clp);
|
||||
clipper->set_transformation(trafo);
|
||||
|
||||
clipper->set_limiting_plane(sinking ?
|
||||
ClippingPlane(Vec3d::UnitZ(), 0.)
|
||||
: ClippingPlane::ClipsNothing());
|
||||
glsafe(::glPushMatrix());
|
||||
glsafe(::glColor3f(1.0f, 0.37f, 0.0f));
|
||||
clipper->render_cut();
|
||||
|
|
|
@ -180,6 +180,7 @@ public:
|
|||
|
||||
void show_supports(bool show);
|
||||
bool are_supports_shown() const { return m_show_supports; }
|
||||
void render_cut() const;
|
||||
|
||||
protected:
|
||||
void on_update() override;
|
||||
|
@ -187,6 +188,8 @@ protected:
|
|||
|
||||
private:
|
||||
bool m_show_supports = false;
|
||||
std::vector<const TriangleMesh*> m_old_meshes;
|
||||
std::vector<std::unique_ptr<MeshClipper>> m_clippers;
|
||||
};
|
||||
|
||||
|
||||
|
|
|
@ -103,7 +103,7 @@ bool GLGizmosManager::init()
|
|||
m_gizmos.emplace_back(new GLGizmoSlaSupports(m_parent, "sla_supports.svg", 6));
|
||||
m_gizmos.emplace_back(new GLGizmoFdmSupports(m_parent, "fdm_supports.svg", 7));
|
||||
m_gizmos.emplace_back(new GLGizmoSeam(m_parent, "seam.svg", 8));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "fdm_supports.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoMmuSegmentation(m_parent, "mmu_segmentation.svg", 9));
|
||||
m_gizmos.emplace_back(new GLGizmoSimplify(m_parent, "cut.svg", 10));
|
||||
|
||||
m_common_gizmos_data.reset(new CommonGizmosDataPool(&m_parent));
|
||||
|
@ -1248,6 +1248,14 @@ bool GLGizmosManager::is_in_editing_mode(bool error_notification) const
|
|||
}
|
||||
|
||||
|
||||
bool GLGizmosManager::is_hiding_instances() const
|
||||
{
|
||||
return (m_common_gizmos_data
|
||||
&& m_common_gizmos_data->instances_hider()
|
||||
&& m_common_gizmos_data->instances_hider()->is_valid());
|
||||
}
|
||||
|
||||
|
||||
int GLGizmosManager::get_shortcut_key(GLGizmosManager::EType type) const
|
||||
{
|
||||
return m_gizmos[type]->get_shortcut_key();
|
||||
|
|
|
@ -122,7 +122,6 @@ private:
|
|||
MouseCapture m_mouse_capture;
|
||||
std::string m_tooltip;
|
||||
bool m_serializing;
|
||||
//std::unique_ptr<CommonGizmosData> m_common_gizmos_data;
|
||||
std::unique_ptr<CommonGizmosDataPool> m_common_gizmos_data;
|
||||
|
||||
public:
|
||||
|
@ -218,6 +217,7 @@ public:
|
|||
bool wants_reslice_supports_on_undo() const;
|
||||
|
||||
bool is_in_editing_mode(bool error_notification = false) const;
|
||||
bool is_hiding_instances() const;
|
||||
|
||||
void render_current_gizmo() const;
|
||||
void render_current_gizmo_for_picking_pass() const;
|
||||
|
|
|
@ -30,34 +30,154 @@ inline void push_style_color(ImGuiCol idx, const ImVec4& col, bool fading_out, f
|
|||
else
|
||||
ImGui::PushStyleColor(idx, col);
|
||||
}
|
||||
// return true if NOT in disabled mode.
|
||||
inline bool disabled_modes_check(const std::string& disabled_modes)
|
||||
enum TagCheckResult
|
||||
{
|
||||
if (disabled_modes.empty())
|
||||
TagCheckAffirmative,
|
||||
TagCheckNegative,
|
||||
TagCheckNotCompatible
|
||||
};
|
||||
// returns if in mode defined by tag
|
||||
TagCheckResult tag_check_mode(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = {"simple", "advanced", "expert"};
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end())
|
||||
{
|
||||
ConfigOptionMode config_mode = wxGetApp().get_mode();
|
||||
if (config_mode == ConfigOptionMode::comSimple) return (tag == "simple" ? TagCheckAffirmative : TagCheckNegative);
|
||||
else if (config_mode == ConfigOptionMode::comAdvanced) return (tag == "advanced" ? TagCheckAffirmative : TagCheckNegative);
|
||||
else if (config_mode == ConfigOptionMode::comExpert) return (tag == "expert" ? TagCheckAffirmative : TagCheckNegative);
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
TagCheckResult tag_check_tech(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = { "FFF", "MMU", "SLA" };
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) {
|
||||
const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
if (tech == ptFFF) {
|
||||
// MMU / FFF
|
||||
bool is_mmu = wxGetApp().extruders_edited_cnt() > 1;
|
||||
if (tag == "MMU") return (is_mmu ? TagCheckAffirmative : TagCheckNegative);
|
||||
return (tag == "FFF" ? TagCheckAffirmative : TagCheckNegative);
|
||||
} else {
|
||||
// SLA
|
||||
return (tag == "SLA" ? TagCheckAffirmative : TagCheckNegative);
|
||||
}
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
TagCheckResult tag_check_system(const std::string& tag)
|
||||
{
|
||||
std::vector<std::string> allowed_tags = { "Windows", "Linux", "OSX" };
|
||||
if (std::find(allowed_tags.begin(), allowed_tags.end(), tag) != allowed_tags.end()) {
|
||||
if (tag =="Windows")
|
||||
#ifdef WIN32
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // WIN32
|
||||
|
||||
if (tag == "Linux")
|
||||
#ifdef __linux__
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // __linux__
|
||||
|
||||
if (tag == "OSX")
|
||||
#ifdef __APPLE__
|
||||
return TagCheckAffirmative;
|
||||
#else
|
||||
return TagCheckNegative;
|
||||
#endif // __apple__
|
||||
}
|
||||
return TagCheckNotCompatible;
|
||||
}
|
||||
|
||||
// return true if NOT in disabled mode.
|
||||
bool tags_check(const std::string& disabled_tags, const std::string& enabled_tags)
|
||||
{
|
||||
if (disabled_tags.empty() && enabled_tags.empty())
|
||||
return true;
|
||||
|
||||
// simple / advanced / expert
|
||||
ConfigOptionMode config_mode = wxGetApp().get_mode();
|
||||
std::string mode_name;
|
||||
if (config_mode == ConfigOptionMode::comSimple) mode_name = "simple";
|
||||
else if (config_mode == ConfigOptionMode::comAdvanced) mode_name = "advanced";
|
||||
else if (config_mode == ConfigOptionMode::comExpert) mode_name = "expert";
|
||||
|
||||
if (!mode_name.empty() && disabled_modes.find(mode_name) != std::string::npos)
|
||||
return false;
|
||||
|
||||
// fff / sla
|
||||
const PrinterTechnology tech = wxGetApp().preset_bundle->printers.get_edited_preset().printer_technology();
|
||||
if (tech == ptFFF) {
|
||||
if (disabled_modes.find("FFF") != std::string::npos)
|
||||
return false;
|
||||
} else {
|
||||
if (disabled_modes.find("SLA") != std::string::npos)
|
||||
return false;
|
||||
}
|
||||
|
||||
// enabled tags must ALL return affirmative or check fails
|
||||
if (!enabled_tags.empty()) {
|
||||
std::string tag;
|
||||
for (size_t i = 0; i < enabled_tags.size(); i++) {
|
||||
if (enabled_tags[i] == ' ') {
|
||||
tag.erase();
|
||||
continue;
|
||||
}
|
||||
if (enabled_tags[i] != ';') {
|
||||
tag += enabled_tags[i];
|
||||
}
|
||||
if (enabled_tags[i] == ';' || i == enabled_tags.size() - 1) {
|
||||
if (!tag.empty()) {
|
||||
TagCheckResult result;
|
||||
result = tag_check_mode(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
result = tag_check_tech(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
result = tag_check_system(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in enabled_tags not compatible.";
|
||||
// non compatible in enabled means return false since all enabled must be affirmative.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// disabled tags must all NOT return affirmative or check fails
|
||||
if (!disabled_tags.empty()) {
|
||||
std::string tag;
|
||||
for (size_t i = 0; i < disabled_tags.size(); i++) {
|
||||
if (disabled_tags[i] == ' ') {
|
||||
tag.erase();
|
||||
continue;
|
||||
}
|
||||
if (disabled_tags[i] != ';') {
|
||||
tag += disabled_tags[i];
|
||||
}
|
||||
if (disabled_tags[i] == ';' || i == disabled_tags.size() - 1) {
|
||||
if (!tag.empty()) {
|
||||
TagCheckResult result;
|
||||
result = tag_check_mode(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
result = tag_check_tech(tag);
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
result = tag_check_system(tag);
|
||||
if (result == TagCheckResult::TagCheckAffirmative)
|
||||
return false;
|
||||
if (result == TagCheckResult::TagCheckNegative)
|
||||
continue;
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint Notification: Tag " << tag << " in disabled_tags not compatible.";
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
void launch_browser_if_allowed(const std::string& url)
|
||||
{
|
||||
if (wxGetApp().app_config->get("suppress_hyperlinks") != "1")
|
||||
wxLaunchDefaultBrowser(url);
|
||||
}
|
||||
} //namespace
|
||||
|
||||
void HintDatabase::init()
|
||||
|
@ -90,13 +210,15 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
dict.emplace(data.first, data.second.data());
|
||||
}
|
||||
|
||||
//unescaping a translating all texts
|
||||
//unescape text1
|
||||
//unescaping and translating all texts and saving all data common for all hint types
|
||||
std::string fulltext;
|
||||
std::string text1;
|
||||
std::string hypertext_text;
|
||||
std::string follow_text;
|
||||
std::string disabled_modes;
|
||||
std::string disabled_tags;
|
||||
std::string enabled_tags;
|
||||
std::string documentation_link;
|
||||
//unescape text1
|
||||
unescape_string_cstyle(_utf8(dict["text"]), fulltext);
|
||||
// replace <b> and </b> for imgui markers
|
||||
std::string marker_s(1, ImGui::ColorMarkerStart);
|
||||
|
@ -143,8 +265,14 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
text1 = fulltext;
|
||||
}
|
||||
|
||||
if (dict.find("disabled_modes") != dict.end()) {
|
||||
disabled_modes = dict["disabled_modes"];
|
||||
if (dict.find("disabled_tags") != dict.end()) {
|
||||
disabled_tags = dict["disabled_tags"];
|
||||
}
|
||||
if (dict.find("enabled_tags") != dict.end()) {
|
||||
enabled_tags = dict["enabled_tags"];
|
||||
}
|
||||
if (dict.find("documentation_link") != dict.end()) {
|
||||
documentation_link = dict["documentation_link"];
|
||||
}
|
||||
|
||||
// create HintData
|
||||
|
@ -152,37 +280,37 @@ void HintDatabase::load_hints_from_file(const boost::filesystem::path& path)
|
|||
//link to internet
|
||||
if(dict["hypertext_type"] == "link") {
|
||||
std::string hypertext_link = dict["hypertext_link"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [hypertext_link]() { wxLaunchDefaultBrowser(hypertext_link); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [hypertext_link]() { launch_browser_if_allowed(hypertext_link); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
// highlight settings
|
||||
} else if (dict["hypertext_type"] == "settings") {
|
||||
std::string opt = dict["hypertext_settings_opt"];
|
||||
Preset::Type type = static_cast<Preset::Type>(std::atoi(dict["hypertext_settings_type"].c_str()));
|
||||
std::wstring category = boost::nowide::widen(dict["hypertext_settings_category"]);
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [opt, type, category]() { GUI::wxGetApp().sidebar().jump_to_option(opt, type, category); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
// open preferences
|
||||
} else if(dict["hypertext_type"] == "preferences") {
|
||||
int page = static_cast<Preset::Type>(std::atoi(dict["hypertext_preferences_page"].c_str()));
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, [page]() { wxGetApp().open_preferences(page); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, [page]() { wxGetApp().open_preferences(page); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
|
||||
} else if (dict["hypertext_type"] == "plater") {
|
||||
std::string item = dict["hypertext_plater_item"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_toolbar_item(item); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
} else if (dict["hypertext_type"] == "gizmo") {
|
||||
std::string item = dict["hypertext_gizmo_item"];
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, true, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, true, documentation_link, [item]() { wxGetApp().plater()->canvas3D()->highlight_gizmo(item); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
else if (dict["hypertext_type"] == "gallery") {
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_modes, false, []() { wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link, []() { wxGetApp().obj_list()->load_shape_object_from_gallery(); } };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
} else {
|
||||
// plain text without hypertext
|
||||
HintData hint_data{ text1 };
|
||||
HintData hint_data{ text1, hypertext_text, follow_text, disabled_tags, enabled_tags, false, documentation_link };
|
||||
m_loaded_hints.emplace_back(hint_data);
|
||||
}
|
||||
}
|
||||
|
@ -194,8 +322,14 @@ HintData* HintDatabase::get_hint(bool up)
|
|||
init();
|
||||
//return false;
|
||||
}
|
||||
if (m_loaded_hints.empty())
|
||||
{
|
||||
BOOST_LOG_TRIVIAL(error) << "There were no hints loaded from hints.ini file.";
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// shift id
|
||||
m_hint_id = (up ? m_hint_id + 1 : (m_hint_id == 0 ? m_loaded_hints.size() - 1 : m_hint_id - 1));
|
||||
m_hint_id = (up ? m_hint_id + 1 : m_hint_id );
|
||||
m_hint_id %= m_loaded_hints.size();
|
||||
|
||||
AppConfig* app_config = wxGetApp().app_config;
|
||||
|
@ -220,12 +354,16 @@ void NotificationManager::HintNotification::count_spaces()
|
|||
std::string text;
|
||||
text = ImGui::WarningMarker;
|
||||
float picture_width = ImGui::CalcTextSize(text.c_str()).x;
|
||||
m_left_indentation = picture_width + m_line_height / 2;
|
||||
m_left_indentation = picture_width * 1.5f + m_line_height / 2;
|
||||
|
||||
// no left button picture
|
||||
//m_left_indentation = m_line_height;
|
||||
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;// 5.5f; // no right arrow
|
||||
if (m_documentation_link.empty())
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;
|
||||
else
|
||||
m_window_width_offset = m_left_indentation + m_line_height * 5.5f;
|
||||
|
||||
m_window_width = m_line_height * 25;
|
||||
}
|
||||
|
||||
|
@ -263,7 +401,7 @@ void NotificationManager::HintNotification::count_lines()
|
|||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset ||
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 4 * 3
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x < (m_window_width - m_window_width_offset) / 5 * 3
|
||||
) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset) / width_of_a);
|
||||
|
@ -333,7 +471,7 @@ void NotificationManager::HintNotification::count_lines()
|
|||
}
|
||||
// when one word longer than line.
|
||||
if (ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x > m_window_width - m_window_width_offset - size_of_last_line ||
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 4 * 3
|
||||
ImGui::CalcTextSize(text.substr(last_end, next_space - last_end).c_str()).x + size_of_last_line < (m_window_width - m_window_width_offset) / 5 * 3
|
||||
) {
|
||||
float width_of_a = ImGui::CalcTextSize("a").x;
|
||||
int letter_count = (int)((m_window_width - m_window_width_offset - size_of_last_line) / width_of_a);
|
||||
|
@ -387,12 +525,12 @@ void NotificationManager::HintNotification::set_next_window_size(ImGuiWrapper& i
|
|||
m_window_height += 1 * m_line_height; // top and bottom
|
||||
*/
|
||||
|
||||
m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 4.f * m_line_height);
|
||||
m_window_height = std::max((m_lines_count + 1.f) * m_line_height, 5.f * m_line_height);
|
||||
}
|
||||
|
||||
bool NotificationManager::HintNotification::on_text_click()
|
||||
{
|
||||
if (m_hypertext_callback != nullptr && (!m_runtime_disable || disabled_modes_check(m_disabled_modes)))
|
||||
if (m_hypertext_callback != nullptr && (!m_runtime_disable || tags_check(m_disabled_tags, m_enabled_tags)))
|
||||
m_hypertext_callback();
|
||||
return false;
|
||||
}
|
||||
|
@ -405,7 +543,7 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con
|
|||
|
||||
float x_offset = m_left_indentation;
|
||||
int last_end = 0;
|
||||
float starting_y = (m_lines_count == 2 ? win_size_y / 2 - m_line_height :(m_lines_count == 1 ? win_size_y / 2 - m_line_height / 2: m_line_height / 2));
|
||||
float starting_y = (m_lines_count < 4 ? m_line_height / 2 * (4 - m_lines_count + 1) : m_line_height / 2);
|
||||
float shift_y = m_line_height;
|
||||
std::string line;
|
||||
|
||||
|
@ -425,8 +563,8 @@ void NotificationManager::HintNotification::render_text(ImGuiWrapper& imgui, con
|
|||
// regural line
|
||||
line = m_text1.substr(last_end, m_endlines[i] - last_end);
|
||||
}
|
||||
// first line is headline
|
||||
if (i == 0) {
|
||||
// first line is headline (for hint notification it must be divided by \n)
|
||||
if (m_text1.find('\n') >= m_endlines[i]) {
|
||||
line = ImGui::ColorMarkerStart + line + ImGui::ColorMarkerEnd;
|
||||
}
|
||||
// Add ImGui::ColorMarkerStart if there is ImGui::ColorMarkerEnd first (start was at prev line)
|
||||
|
@ -524,16 +662,17 @@ void NotificationManager::HintNotification::render_close_button(ImGuiWrapper& im
|
|||
close();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
|
||||
|
||||
render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
//render_right_arrow_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_logo(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
render_preferences_button(imgui, win_pos_x, win_pos_y);
|
||||
if (!m_documentation_link.empty() && wxGetApp().app_config->get("suppress_hyperlinks") != "1")
|
||||
{
|
||||
render_documentation_button(imgui, win_size_x, win_size_y, win_pos_x, win_pos_y);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapper& imgui, const float win_pos_x, const float win_pos_y)
|
||||
|
@ -548,12 +687,23 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
|
|||
std::string button_text;
|
||||
button_text = ImGui::PreferencesButton;
|
||||
//hover
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 10.f, win_pos_y + m_window_height - 2 * m_line_height + 1),
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos_x - m_window_width / 15.f, win_pos_y + m_window_height - 1.75f * m_line_height),
|
||||
ImVec2(win_pos_x, win_pos_y + m_window_height),
|
||||
true))
|
||||
{
|
||||
true)) {
|
||||
button_text = ImGui::PreferencesHoverButton;
|
||||
}
|
||||
// tooltip
|
||||
long time_now = wxGetLocalTime();
|
||||
if (m_prefe_hover_time > 0 && m_prefe_hover_time < time_now) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(_u8L("Open Preferences."));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (m_prefe_hover_time == 0)
|
||||
m_prefe_hover_time = time_now;
|
||||
} else
|
||||
m_prefe_hover_time = 0;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
|
@ -568,15 +718,10 @@ void NotificationManager::HintNotification::render_preferences_button(ImGuiWrapp
|
|||
wxGetApp().open_preferences(2);
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
// preferences button is in place of minimize button
|
||||
m_minimize_b_visible = true;
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
// Used for debuging
|
||||
|
@ -605,11 +750,7 @@ void NotificationManager::HintNotification::render_right_arrow_button(ImGuiWrapp
|
|||
retrieve_data();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
|
@ -621,32 +762,95 @@ void NotificationManager::HintNotification::render_logo(ImGuiWrapper& imgui, con
|
|||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::string button_text;
|
||||
button_text = ImGui::ErrorMarker;//LeftArrowButton;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(button_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
std::wstring button_text;
|
||||
button_text = ImGui::ClippyMarker;//LeftArrowButton;
|
||||
std::string placeholder_text;
|
||||
placeholder_text = ImGui::EjectButton;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f * 2.f, button_pic_size.y * 1.25f * 2.f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y * 1.1f);
|
||||
ImGui::SetCursorPosX(0);
|
||||
// shouldnt it render as text?
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
void NotificationManager::HintNotification::retrieve_data(size_t recursion_counter)
|
||||
void NotificationManager::HintNotification::render_documentation_button(ImGuiWrapper& imgui, const float win_size_x, const float win_size_y, const float win_pos_x, const float win_pos_y)
|
||||
{
|
||||
HintData* hint_data = HintDatabase::get_instance().get_hint(true);
|
||||
if (hint_data != nullptr && !disabled_modes_check(hint_data->disabled_modes))
|
||||
ImVec2 win_size(win_size_x, win_size_y);
|
||||
ImVec2 win_pos(win_pos_x, win_pos_y);
|
||||
ImGui::PushStyleColor(ImGuiCol_Button, ImVec4(.0f, .0f, .0f, .0f));
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonHovered, ImVec4(.0f, .0f, .0f, .0f));
|
||||
push_style_color(ImGuiCol_Text, ImVec4(1.f, 1.f, 1.f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_TextSelectedBg, ImVec4(0, .75f, .75f, 1.f), m_state == EState::FadingOut, m_current_fade_opacity);
|
||||
ImGui::PushStyleColor(ImGuiCol_ButtonActive, ImVec4(.0f, .0f, .0f, .0f));
|
||||
|
||||
std::wstring button_text;
|
||||
button_text = ImGui::DocumentationButton;
|
||||
std::string placeholder_text;
|
||||
placeholder_text = ImGui::EjectButton;
|
||||
|
||||
if (ImGui::IsMouseHoveringRect(ImVec2(win_pos.x - m_line_height * 5.f, win_pos.y),
|
||||
ImVec2(win_pos.x - m_line_height * 2.5f, win_pos.y + win_size.y - 2 * m_line_height),
|
||||
true))
|
||||
{
|
||||
button_text = ImGui::DocumentationHoverButton;
|
||||
// tooltip
|
||||
long time_now = wxGetLocalTime();
|
||||
if (m_docu_hover_time > 0 && m_docu_hover_time < time_now) {
|
||||
ImGui::PushStyleColor(ImGuiCol_PopupBg, ImGuiWrapper::COL_WINDOW_BACKGROUND);
|
||||
ImGui::BeginTooltip();
|
||||
imgui.text(_u8L("Open Documentation in web browser."));
|
||||
ImGui::EndTooltip();
|
||||
ImGui::PopStyleColor();
|
||||
}
|
||||
if (m_docu_hover_time == 0)
|
||||
m_docu_hover_time = time_now;
|
||||
}
|
||||
else
|
||||
m_docu_hover_time = 0;
|
||||
|
||||
ImVec2 button_pic_size = ImGui::CalcTextSize(placeholder_text.c_str());
|
||||
ImVec2 button_size(button_pic_size.x * 1.25f, button_pic_size.y * 1.25f);
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 5.0f);
|
||||
ImGui::SetCursorPosY(win_size.y / 2 - button_size.y);
|
||||
if (imgui.button(button_text.c_str(), button_size.x, button_size.y))
|
||||
{
|
||||
open_documentation();
|
||||
}
|
||||
|
||||
//invisible large button
|
||||
ImGui::SetCursorPosX(win_size.x - m_line_height * 4.625f);
|
||||
ImGui::SetCursorPosY(0);
|
||||
if (imgui.button(" ", m_line_height * 2.f, win_size.y - 2 * m_line_height))
|
||||
{
|
||||
open_documentation();
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::HintNotification::open_documentation()
|
||||
{
|
||||
if (!m_documentation_link.empty())
|
||||
{
|
||||
launch_browser_if_allowed(m_documentation_link);
|
||||
}
|
||||
}
|
||||
void NotificationManager::HintNotification::retrieve_data(int recursion_counter)
|
||||
{
|
||||
HintData* hint_data = HintDatabase::get_instance().get_hint(recursion_counter >= 0 ? true : false);
|
||||
if (hint_data == nullptr)
|
||||
close();
|
||||
|
||||
if (hint_data != nullptr && !tags_check(hint_data->disabled_tags, hint_data->enabled_tags))
|
||||
{
|
||||
// Content for different user - retrieve another
|
||||
size_t count = HintDatabase::get_instance().get_count();
|
||||
if (count < recursion_counter) {
|
||||
if ((int)count < recursion_counter) {
|
||||
BOOST_LOG_TRIVIAL(error) << "Hint notification failed to load data due to recursion counter.";
|
||||
} else {
|
||||
retrieve_data(recursion_counter + 1);
|
||||
|
@ -661,12 +865,13 @@ void NotificationManager::HintNotification::retrieve_data(size_t recursion_count
|
|||
hint_data->text,
|
||||
hint_data->hypertext, nullptr,
|
||||
hint_data->follow_text };
|
||||
update(nd);
|
||||
m_hypertext_callback = hint_data->callback;
|
||||
m_disabled_modes = hint_data->disabled_modes;
|
||||
m_runtime_disable = hint_data->runtime_disable;
|
||||
m_has_hint_data = true;
|
||||
|
||||
m_disabled_tags = hint_data->disabled_tags;
|
||||
m_enabled_tags = hint_data->enabled_tags;
|
||||
m_runtime_disable = hint_data->runtime_disable;
|
||||
m_documentation_link = hint_data->documentation_link;
|
||||
m_has_hint_data = true;
|
||||
update(nd);
|
||||
}
|
||||
}
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -12,9 +12,11 @@ struct HintData
|
|||
std::string text;
|
||||
std::string hypertext;
|
||||
std::string follow_text;
|
||||
std::string disabled_modes;
|
||||
std::string disabled_tags;
|
||||
std::string enabled_tags;
|
||||
bool runtime_disable; // if true - hyperlink will check before every click if not in disabled mode
|
||||
std::function<void(void)> callback{ nullptr };
|
||||
std::string documentation_link;
|
||||
std::function<void(void)> callback { nullptr };
|
||||
};
|
||||
|
||||
class HintDatabase
|
||||
|
@ -53,12 +55,13 @@ private:
|
|||
class NotificationManager::HintNotification : public NotificationManager::PopNotification
|
||||
{
|
||||
public:
|
||||
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler)
|
||||
HintNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, bool new_hint)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
{
|
||||
retrieve_data();
|
||||
retrieve_data(new_hint ? 0 : -1);
|
||||
}
|
||||
virtual void init() override;
|
||||
void open_next() { retrieve_data(0); }
|
||||
protected:
|
||||
virtual void set_next_window_size(ImGuiWrapper& imgui) override;
|
||||
virtual void count_spaces() override;
|
||||
|
@ -77,18 +80,27 @@ protected:
|
|||
void render_right_arrow_button(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_documentation_button(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
void render_logo(ImGuiWrapper& imgui,
|
||||
const float win_size_x, const float win_size_y,
|
||||
const float win_pos_x, const float win_pos_y);
|
||||
|
||||
void retrieve_data(size_t recursion_counter = 0);
|
||||
// recursion counter -1 tells to retrieve same hint as last time
|
||||
void retrieve_data(int recursion_counter = 0);
|
||||
void open_documentation();
|
||||
|
||||
bool m_has_hint_data { false };
|
||||
std::function<void(void)> m_hypertext_callback;
|
||||
std::string m_disabled_modes;
|
||||
std::string m_disabled_tags;
|
||||
std::string m_enabled_tags;
|
||||
bool m_runtime_disable;
|
||||
std::string m_documentation_link;
|
||||
float m_close_b_y { 0 };
|
||||
float m_close_b_w { 0 };
|
||||
// hover of buttons
|
||||
long m_docu_hover_time { 0 };
|
||||
long m_prefe_hover_time{ 0 };
|
||||
};
|
||||
|
||||
} //namespace Slic3r
|
||||
|
|
|
@ -36,7 +36,7 @@ namespace Slic3r {
|
|||
namespace GUI {
|
||||
|
||||
|
||||
static const std::map<const char, std::string> font_icons = {
|
||||
static const std::map<const wchar_t, std::string> font_icons = {
|
||||
{ImGui::PrintIconMarker , "cog" },
|
||||
{ImGui::PrinterIconMarker , "printer" },
|
||||
{ImGui::PrinterSlaIconMarker , "sla_printer" },
|
||||
|
@ -49,21 +49,27 @@ static const std::map<const char, std::string> font_icons = {
|
|||
{ImGui::PreferencesButton , "notification_preferences" },
|
||||
{ImGui::PreferencesHoverButton , "notification_preferences_hover"},
|
||||
};
|
||||
static const std::map<const char, std::string> font_icons_large = {
|
||||
{ImGui::CloseNotifButton , "notification_close" },
|
||||
{ImGui::CloseNotifHoverButton , "notification_close_hover" },
|
||||
{ImGui::EjectButton , "notification_eject_sd" },
|
||||
{ImGui::EjectHoverButton , "notification_eject_sd_hover" },
|
||||
{ImGui::WarningMarker , "notification_warning" },
|
||||
{ImGui::ErrorMarker , "notification_error" },
|
||||
{ImGui::CancelButton , "notification_cancel" },
|
||||
{ImGui::CancelHoverButton , "notification_cancel_hover" },
|
||||
{ImGui::SinkingObjectMarker , "move" },
|
||||
{ImGui::CustomSupportsMarker , "fdm_supports" },
|
||||
{ImGui::CustomSeamMarker , "seam" },
|
||||
{ImGui::MmuSegmentationMarker , "move" },
|
||||
{ImGui::VarLayerHeightMarker , "layers" },
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_large = {
|
||||
{ImGui::CloseNotifButton , "notification_close" },
|
||||
{ImGui::CloseNotifHoverButton , "notification_close_hover" },
|
||||
{ImGui::EjectButton , "notification_eject_sd" },
|
||||
{ImGui::EjectHoverButton , "notification_eject_sd_hover" },
|
||||
{ImGui::WarningMarker , "notification_warning" },
|
||||
{ImGui::ErrorMarker , "notification_error" },
|
||||
{ImGui::CancelButton , "notification_cancel" },
|
||||
{ImGui::CancelHoverButton , "notification_cancel_hover" },
|
||||
{ImGui::SinkingObjectMarker , "move" },
|
||||
{ImGui::CustomSupportsMarker , "fdm_supports" },
|
||||
{ImGui::CustomSeamMarker , "seam" },
|
||||
{ImGui::MmuSegmentationMarker , "mmu_segmentation" },
|
||||
{ImGui::VarLayerHeightMarker , "layers" },
|
||||
{ImGui::DocumentationButton , "notification_documentation" },
|
||||
{ImGui::DocumentationHoverButton, "notification_documentation_hover"},
|
||||
};
|
||||
|
||||
static const std::map<const wchar_t, std::string> font_icons_extra_large = {
|
||||
{ImGui::ClippyMarker , "notification_clippy" },
|
||||
|
||||
};
|
||||
|
||||
const ImVec4 ImGuiWrapper::COL_GREY_DARK = { 0.333f, 0.333f, 0.333f, 1.0f };
|
||||
|
@ -989,6 +995,8 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz, icon_sz, 3.0 * font_scale + icon_sz);
|
||||
for (auto& icon : font_icons_large)
|
||||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 2, icon_sz * 2, 3.0 * font_scale + icon_sz * 2);
|
||||
for (auto& icon : font_icons_extra_large)
|
||||
io.Fonts->AddCustomRectFontGlyph(font, icon.first, icon_sz * 4, icon_sz * 4, 3.0 * font_scale + icon_sz * 4);
|
||||
|
||||
// Build texture atlas
|
||||
unsigned char* pixels;
|
||||
|
@ -1027,6 +1035,22 @@ void ImGuiWrapper::init_font(bool compress)
|
|||
rect_id++;
|
||||
}
|
||||
|
||||
icon_sz *= 2; // default size of extra large icon is 64 px
|
||||
for (auto icon : font_icons_extra_large) {
|
||||
if (const ImFontAtlas::CustomRect* rect = io.Fonts->GetCustomRectByIndex(rect_id)) {
|
||||
assert(rect->Width == icon_sz);
|
||||
assert(rect->Height == icon_sz);
|
||||
std::vector<unsigned char> raw_data = load_svg(icon.second, icon_sz, icon_sz);
|
||||
const ImU32* pIn = (ImU32*)raw_data.data();
|
||||
for (int y = 0; y < icon_sz; y++) {
|
||||
ImU32* pOut = (ImU32*)pixels + (rect->Y + y) * width + (rect->X);
|
||||
for (int x = 0; x < icon_sz; x++)
|
||||
*pOut++ = *pIn++;
|
||||
}
|
||||
}
|
||||
rect_id++;
|
||||
}
|
||||
|
||||
// Upload texture to graphics system
|
||||
GLint last_texture;
|
||||
glsafe(::glGetIntegerv(GL_TEXTURE_BINDING_2D, &last_texture));
|
||||
|
|
|
@ -33,7 +33,7 @@ public:
|
|||
|
||||
m_filepicker = new wxFilePickerCtrl(this, wxID_ANY,
|
||||
from_u8(wxGetApp().app_config->get_last_dir()), _(L("Choose SLA archive:")),
|
||||
"SL1 archive files (*.sl1, *.zip)|*.sl1;*.SL1;*.zip;*.ZIP",
|
||||
"SL1 / SL1S archive files (*.sl1, *.sl1s, *.zip)|*.sl1;*.SL1;*.sl1s;*.SL1S;*.zip;*.ZIP",
|
||||
wxDefaultPosition, wxDefaultSize, wxFLP_DEFAULT_STYLE | wxFD_OPEN | wxFD_FILE_MUST_EXIST);
|
||||
|
||||
szfilepck->Add(new wxStaticText(this, wxID_ANY, _L("Import file") + ": "), 0, wxALIGN_CENTER);
|
||||
|
@ -119,6 +119,7 @@ public:
|
|||
wxString path;
|
||||
Vec2i win = {2, 2};
|
||||
std::string err;
|
||||
ConfigSubstitutions config_substitutions;
|
||||
|
||||
priv(Plater *plt) : plater{plt} {}
|
||||
};
|
||||
|
@ -140,27 +141,22 @@ void SLAImportJob::process()
|
|||
if (p->path.empty()) return;
|
||||
|
||||
std::string path = p->path.ToUTF8().data();
|
||||
ConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
switch (p->sel) {
|
||||
case Sel::modelAndProfile:
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, p->profile, progr);
|
||||
break;
|
||||
case Sel::modelOnly:
|
||||
config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
|
||||
p->config_substitutions = import_sla_archive(path, p->win, p->mesh, progr);
|
||||
break;
|
||||
case Sel::profileOnly:
|
||||
config_substitutions = import_sla_archive(path, p->profile);
|
||||
p->config_substitutions = import_sla_archive(path, p->profile);
|
||||
break;
|
||||
}
|
||||
|
||||
} catch (std::exception &ex) {
|
||||
p->err = ex.what();
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
//FIXME Add reporting here "Loading profiles found following incompatibilities."
|
||||
}
|
||||
|
||||
update_status(100, was_canceled() ? _(L("Importing canceled.")) :
|
||||
_(L("Importing done.")));
|
||||
|
@ -187,6 +183,7 @@ void SLAImportJob::prepare()
|
|||
p->path = !nm.Exists(wxFILE_EXISTS_REGULAR) ? "" : nm.GetFullPath();
|
||||
p->sel = dlg.get_selection();
|
||||
p->win = dlg.get_marchsq_windowsize();
|
||||
p->config_substitutions.clear();
|
||||
} else {
|
||||
p->path = "";
|
||||
}
|
||||
|
@ -230,8 +227,11 @@ void SLAImportJob::finalize()
|
|||
p->plater->sidebar().obj_list()->load_mesh_object(TriangleMesh{p->mesh},
|
||||
name, is_centered);
|
||||
}
|
||||
|
||||
|
||||
if (! p->config_substitutions.empty())
|
||||
show_substitutions_info(p->config_substitutions, p->path.ToUTF8().data());
|
||||
|
||||
reset();
|
||||
}
|
||||
|
||||
}}
|
||||
}} // namespace Slic3r::GUI
|
||||
|
|
|
@ -151,6 +151,9 @@ void KBShortcutsDialog::fill_shortcuts()
|
|||
{ "F", L("Gizmo Place face on bed") },
|
||||
{ "H", L("Gizmo SLA hollow") },
|
||||
{ "L", L("Gizmo SLA support points") },
|
||||
{ "L", L("Gizmo FDM paint-on supports") },
|
||||
{ "P", L("Gizmo FDM paint-on seam") },
|
||||
{ "N", L("Gizmo Multi Material painting") },
|
||||
{ "Esc", L("Unselect gizmo or clear selection") },
|
||||
{ "K", L("Change camera type (perspective, orthographic)") },
|
||||
{ "B", L("Zoom to Bed") },
|
||||
|
|
|
@ -45,6 +45,7 @@
|
|||
#include "GUI_Factories.hpp"
|
||||
#include "GUI_ObjectList.hpp"
|
||||
#include "GalleryDialog.hpp"
|
||||
#include "NotificationManager.hpp"
|
||||
|
||||
#ifdef _WIN32
|
||||
#include <dbt.h>
|
||||
|
@ -219,15 +220,19 @@ DPIFrame(NULL, wxID_ANY, "", wxDefaultPosition, wxDefaultSize, wxDEFAULT_FRAME_S
|
|||
return;
|
||||
}
|
||||
|
||||
if (m_plater != nullptr && !m_plater->save_project_if_dirty()) {
|
||||
event.Veto();
|
||||
return;
|
||||
if (m_plater != nullptr) {
|
||||
int saved_project = m_plater->save_project_if_dirty();
|
||||
if (saved_project == wxID_CANCEL) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
// check unsaved changes only if project wasn't saved
|
||||
else if (saved_project == wxID_NO && event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (event.CanVeto() && !wxGetApp().check_and_save_current_preset_changes()) {
|
||||
event.Veto();
|
||||
return;
|
||||
}
|
||||
if (event.CanVeto() && !wxGetApp().check_print_host_queue()) {
|
||||
event.Veto();
|
||||
return;
|
||||
|
@ -1073,6 +1078,8 @@ static wxMenu* generate_help_menu()
|
|||
else
|
||||
append_menu_item(helpMenu, wxID_ANY, wxString::Format(_L("&About %s"), GCODEVIEWER_APP_NAME), _L("Show about dialog"),
|
||||
[](wxCommandEvent&) { Slic3r::GUI::about(); });
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Show Tip of the day"), _L("Opens Tip of the day notification in bottom right corner or shows another tip if already opened."),
|
||||
[](wxCommandEvent&) { wxGetApp().plater()->get_notification_manager()->push_hint_notification(false); });
|
||||
helpMenu->AppendSeparator();
|
||||
append_menu_item(helpMenu, wxID_ANY, _L("Keyboard Shortcuts") + sep + "&?", _L("Show the list of the keyboard shortcuts"),
|
||||
[](wxCommandEvent&) { wxGetApp().keyboard_shortcuts(); });
|
||||
|
@ -1181,7 +1188,7 @@ void MainFrame::init_menubar_as_editor()
|
|||
[this](wxCommandEvent&) { if (m_plater) m_plater->add_model(true); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr; }, this);
|
||||
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 archive") + dots, _L("Load an SL1 archive"),
|
||||
append_menu_item(import_menu, wxID_ANY, _L("Import SL1 / SL1S archive") + dots, _L("Load an SL1 / Sl1S archive"),
|
||||
[this](wxCommandEvent&) { if (m_plater) m_plater->import_sl1_archive(); }, "import_plater", nullptr,
|
||||
[this](){return m_plater != nullptr; }, this);
|
||||
|
||||
|
@ -1742,12 +1749,8 @@ bool MainFrame::load_config_file(const std::string &path)
|
|||
{
|
||||
try {
|
||||
ConfigSubstitutions config_substitutions = wxGetApp().preset_bundle->load_config_file(path, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (!config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions, path);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return false;
|
||||
|
@ -1806,18 +1809,16 @@ void MainFrame::load_configbundle(wxString file/* = wxEmptyString, const bool re
|
|||
size_t presets_imported = 0;
|
||||
PresetsConfigSubstitutions config_substitutions;
|
||||
try {
|
||||
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported);
|
||||
// Report all substitutions.
|
||||
std::tie(config_substitutions, presets_imported) = wxGetApp().preset_bundle->load_configbundle(
|
||||
file.ToUTF8().data(), PresetBundle::LoadConfigBundleAttribute::SaveImported, ForwardCompatibilitySubstitutionRule::Enable);
|
||||
} catch (const std::exception &ex) {
|
||||
show_error(this, ex.what());
|
||||
return;
|
||||
}
|
||||
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO: Add list of changes from all_substitutions
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions);
|
||||
|
||||
// Load the currently selected preset into the GUI, update the preset selection box.
|
||||
wxGetApp().load_current_presets();
|
||||
|
|
|
@ -24,6 +24,15 @@ void MeshClipper::set_plane(const ClippingPlane& plane)
|
|||
}
|
||||
|
||||
|
||||
void MeshClipper::set_limiting_plane(const ClippingPlane& plane)
|
||||
{
|
||||
if (m_limiting_plane != plane) {
|
||||
m_limiting_plane = plane;
|
||||
m_triangles_valid = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
void MeshClipper::set_mesh(const TriangleMesh& mesh)
|
||||
{
|
||||
|
@ -78,29 +87,82 @@ void MeshClipper::recalculate_triangles()
|
|||
float height_mesh = m_plane.distance(m_trafo.get_offset()) * (up_noscale.norm()/up.norm());
|
||||
|
||||
// Now do the cutting
|
||||
MeshSlicingParamsEx slicing_params;
|
||||
MeshSlicingParams slicing_params;
|
||||
slicing_params.trafo.rotate(Eigen::Quaternion<double, Eigen::DontAlign>::FromTwoVectors(up, Vec3d::UnitZ()));
|
||||
|
||||
assert(m_mesh->has_shared_vertices());
|
||||
std::vector<ExPolygons> list_of_expolys = slice_mesh_ex(m_mesh->its, std::vector<float>{height_mesh}, slicing_params);
|
||||
ExPolygons expolys = union_ex(slice_mesh(m_mesh->its, height_mesh, slicing_params));
|
||||
|
||||
if (m_negative_mesh && !m_negative_mesh->empty()) {
|
||||
assert(m_negative_mesh->has_shared_vertices());
|
||||
std::vector<ExPolygons> neg_polys = slice_mesh_ex(m_negative_mesh->its, std::vector<float>{height_mesh}, slicing_params);
|
||||
list_of_expolys.front() = diff_ex(list_of_expolys.front(), neg_polys.front());
|
||||
ExPolygons neg_expolys = union_ex(slice_mesh(m_negative_mesh->its, height_mesh, slicing_params));
|
||||
expolys = diff_ex(expolys, neg_expolys);
|
||||
}
|
||||
|
||||
m_triangles2d = triangulate_expolygons_2f(list_of_expolys[0], m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
|
||||
// Rotate the cut into world coords:
|
||||
// Triangulate and rotate the cut into world coords:
|
||||
Eigen::Quaterniond q;
|
||||
q.setFromTwoVectors(Vec3d::UnitZ(), up);
|
||||
Transform3d tr = Transform3d::Identity();
|
||||
tr.rotate(q);
|
||||
tr = m_trafo.get_matrix() * tr;
|
||||
height_mesh += 0.001f; // to avoid z-fighting
|
||||
|
||||
// to avoid z-fighting
|
||||
height_mesh += 0.001f;
|
||||
if (m_limiting_plane != ClippingPlane::ClipsNothing())
|
||||
{
|
||||
// Now remove whatever ended up below the limiting plane (e.g. sinking objects).
|
||||
// First transform the limiting plane from world to mesh coords.
|
||||
// Note that inverse of tr transforms the plane from world to horizontal.
|
||||
Vec3d normal_old = m_limiting_plane.get_normal().normalized();
|
||||
Vec3d normal_new = (tr.matrix().block<3,3>(0,0).transpose() * normal_old).normalized();
|
||||
|
||||
// normal_new should now be the plane normal in mesh coords. To find the offset,
|
||||
// transform a point and set offset so it belongs to the transformed plane.
|
||||
Vec3d pt = Vec3d::Zero();
|
||||
double plane_offset = m_limiting_plane.get_data()[3];
|
||||
if (std::abs(normal_old.z()) > 0.5) // normal is normalized, at least one of the coords if larger than sqrt(3)/3 = 0.57
|
||||
pt.z() = - plane_offset / normal_old.z();
|
||||
else if (std::abs(normal_old.y()) > 0.5)
|
||||
pt.y() = - plane_offset / normal_old.y();
|
||||
else
|
||||
pt.x() = - plane_offset / normal_old.x();
|
||||
pt = tr.inverse() * pt;
|
||||
double offset = -(normal_new.dot(pt));
|
||||
|
||||
if (std::abs(normal_old.dot(m_plane.get_normal().normalized())) > 0.99) {
|
||||
// The cuts are parallel, show all or nothing.
|
||||
if (offset < height_mesh)
|
||||
expolys.clear();
|
||||
} else {
|
||||
// The cut is a horizontal plane defined by z=height_mesh.
|
||||
// ax+by+e=0 is the line of intersection with the limiting plane.
|
||||
// Normalized so a^2 + b^2 = 1.
|
||||
double len = std::hypot(normal_new.x(), normal_new.y());
|
||||
if (len == 0.)
|
||||
return;
|
||||
double a = normal_new.x() / len;
|
||||
double b = normal_new.y() / len;
|
||||
double e = (normal_new.z() * height_mesh + offset) / len;
|
||||
if (b == 0.)
|
||||
return;
|
||||
|
||||
// We need a half-plane to limit the cut. Get angle of the intersecting line.
|
||||
double angle = std::atan(-a/b);
|
||||
if (b > 0) // select correct half-plane
|
||||
angle += M_PI;
|
||||
|
||||
// We'll take a big rectangle above x-axis and rotate and translate
|
||||
// it so it lies on our line. This will be the figure to subtract
|
||||
// from the cut. The coordinates must not overflow after the transform,
|
||||
// make the rectangle a bit smaller.
|
||||
coord_t size = (std::numeric_limits<coord_t>::max() - scale_(std::max(std::abs(e*a), std::abs(e*b)))) / 4;
|
||||
Polygons ep {Polygon({Point(-size, 0), Point(size, 0), Point(size, 2*size), Point(-size, 2*size)})};
|
||||
ep.front().rotate(angle);
|
||||
ep.front().translate(scale_(-e * a), scale_(-e * b));
|
||||
expolys = diff_ex(expolys, ep);
|
||||
}
|
||||
}
|
||||
|
||||
m_triangles2d = triangulate_expolygons_2f(expolys, m_trafo.get_matrix().matrix().determinant() < 0.);
|
||||
|
||||
m_vertex_array.release_geometry();
|
||||
for (auto it=m_triangles2d.cbegin(); it != m_triangles2d.cend(); it=it+3) {
|
||||
|
@ -159,17 +221,19 @@ bool MeshRaycaster::unproject_on_mesh(const Vec2d& mouse_pos, const Transform3d&
|
|||
|
||||
unsigned i = 0;
|
||||
|
||||
// Remove points that are obscured or cut by the clipping plane
|
||||
if (clipping_plane) {
|
||||
for (i=0; i<hits.size(); ++i)
|
||||
if (! clipping_plane->is_point_clipped(trafo * hits[i].position()))
|
||||
break;
|
||||
// Remove points that are obscured or cut by the clipping plane.
|
||||
// Also, remove anything below the bed (sinking objects).
|
||||
for (i=0; i<hits.size(); ++i) {
|
||||
Vec3d transformed_hit = trafo * hits[i].position();
|
||||
if (transformed_hit.z() >= 0. &&
|
||||
(! clipping_plane || ! clipping_plane->is_point_clipped(transformed_hit)))
|
||||
break;
|
||||
}
|
||||
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
// All hits are either clipped, or there is an odd number of unclipped
|
||||
// hits - meaning the nearest must be from inside the mesh.
|
||||
return false;
|
||||
}
|
||||
if (i==hits.size() || (hits.size()-i) % 2 != 0) {
|
||||
// All hits are either clipped, or there is an odd number of unclipped
|
||||
// hits - meaning the nearest must be from inside the mesh.
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now stuff the points in the provided vector and calculate normals if asked about them:
|
||||
|
|
|
@ -71,6 +71,11 @@ public:
|
|||
// This is supposed to be in world coordinates.
|
||||
void set_plane(const ClippingPlane& plane);
|
||||
|
||||
// In case the object is clipped by two planes (e.g. in case of sinking
|
||||
// objects), this will be used to clip the triagnulated cut.
|
||||
// Pass ClippingPlane::ClipsNothing to turn this off.
|
||||
void set_limiting_plane(const ClippingPlane& plane);
|
||||
|
||||
// Which mesh to cut. MeshClipper remembers const * to it, caller
|
||||
// must make sure that it stays valid.
|
||||
void set_mesh(const TriangleMesh& mesh);
|
||||
|
@ -92,6 +97,7 @@ private:
|
|||
const TriangleMesh* m_mesh = nullptr;
|
||||
const TriangleMesh* m_negative_mesh = nullptr;
|
||||
ClippingPlane m_plane;
|
||||
ClippingPlane m_limiting_plane = ClippingPlane::ClipsNothing();
|
||||
std::vector<Vec2f> m_triangles2d;
|
||||
GLIndexedVertexArray m_vertex_array;
|
||||
bool m_triangles_valid = false;
|
||||
|
|
|
@ -61,7 +61,7 @@ MsgDialog::MsgDialog(wxWindow *parent, const wxString &title, const wxString &he
|
|||
logo = new wxStaticBitmap(this, wxID_ANY, wxNullBitmap);
|
||||
|
||||
topsizer->Add(logo, 0, wxALL, BORDER);
|
||||
topsizer->Add(rightsizer, 1, wxALL | wxEXPAND, BORDER);
|
||||
topsizer->Add(rightsizer, 1, wxTOP | wxBOTTOM | wxRIGHT | wxEXPAND, BORDER);
|
||||
|
||||
SetSizerAndFit(topsizer);
|
||||
}
|
||||
|
@ -98,7 +98,6 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
msg_lines++;
|
||||
}
|
||||
|
||||
html->SetMinSize(wxSize(40 * wxGetApp().em_unit(), monospaced_font ? 30 * wxGetApp().em_unit() : 2 * msg_lines * wxGetApp().em_unit()));
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxFont monospace = wxGetApp().code_font();
|
||||
wxColour text_clr = wxGetApp().get_label_clr_default();
|
||||
|
@ -109,6 +108,30 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
|
||||
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
|
||||
// calculate html page size from text
|
||||
wxSize page_size;
|
||||
int em = wxGetApp().em_unit();
|
||||
|
||||
// if message containes the table
|
||||
if (msg.Contains("<tr>")) {
|
||||
int lines = msg.Freq('\n') + 1;
|
||||
int pos = 0;
|
||||
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
|
||||
pos = msg.find("<tr>", pos + 1);
|
||||
lines += 2;
|
||||
}
|
||||
int page_height = std::min(int(font.GetPixelSize().y+2) * lines, 68 * em);
|
||||
page_size = wxSize(68 * em, page_height);
|
||||
}
|
||||
else {
|
||||
wxClientDC dc(parent);
|
||||
wxSize msg_sz = dc.GetMultiLineTextExtent(msg);
|
||||
page_size = wxSize(std::min(msg_sz.GetX() + 2 * em, 68 * em),
|
||||
std::min(msg_sz.GetY() + 2 * em, 68 * em));
|
||||
}
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data());
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
|
@ -116,7 +139,7 @@ static void add_msg_content(wxWindow* parent, wxBoxSizer* content_sizer, wxStrin
|
|||
// Code formatting will be preserved. This is useful for reporting errors from the placeholder parser.
|
||||
msg_escaped = std::string("<pre><code>") + msg_escaped + "</code></pre>";
|
||||
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
|
||||
content_sizer->Add(html, 1, wxEXPAND | wxBOTTOM, 30);
|
||||
content_sizer->Add(html, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
// ErrorDialog
|
||||
|
@ -131,7 +154,7 @@ ErrorDialog::ErrorDialog(wxWindow *parent, const wxString &msg, bool monospaced_
|
|||
add_btn(wxID_OK, true);
|
||||
|
||||
// Use a small bitmap with monospaced font, as the error text will not be wrapped.
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/92));
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, monospaced_font ? 48 : /*1*/84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
||||
|
@ -155,7 +178,7 @@ WarningDialog::WarningDialog(wxWindow *parent,
|
|||
if (style & wxYES) add_btn(wxID_YES);
|
||||
if (style & wxNO) add_btn(wxID_NO);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 90));
|
||||
logo->SetBitmap(create_scaled_bitmap("PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
|
@ -179,8 +202,8 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
if (style & wxCANCEL) add_btn(wxID_CANCEL);
|
||||
|
||||
logo->SetBitmap(create_scaled_bitmap(style & wxICON_WARNING ? "exclamation" :
|
||||
style & wxICON_INFORMATION ? "info.png" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer_192px_grayscale.png", this, 90));
|
||||
style & wxICON_INFORMATION ? "info" :
|
||||
style & wxICON_QUESTION ? "question" : "PrusaSlicer_192px_grayscale.png", this, 84));
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
Fit();
|
||||
|
@ -188,5 +211,57 @@ MessageDialog::MessageDialog(wxWindow* parent,
|
|||
}
|
||||
#endif
|
||||
|
||||
|
||||
// InfoDialog
|
||||
|
||||
InfoDialog::InfoDialog(wxWindow* parent, const wxString &title, const wxString& msg)
|
||||
: MsgDialog(parent, wxString::Format(_L("%s information"), SLIC3R_APP_NAME), title)
|
||||
, msg(msg)
|
||||
{
|
||||
this->SetBackgroundColour(wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW));
|
||||
|
||||
// Text shown as HTML, so that mouse selection and Ctrl-V to copy will work.
|
||||
wxHtmlWindow* html = new wxHtmlWindow(this, wxID_ANY, wxDefaultPosition, wxDefaultSize, wxHW_SCROLLBAR_AUTO);
|
||||
{
|
||||
wxFont font = wxSystemSettings::GetFont(wxSYS_DEFAULT_GUI_FONT);
|
||||
wxFont monospace = wxGetApp().code_font();
|
||||
wxColour text_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOWTEXT);
|
||||
wxColour bgr_clr = wxSystemSettings::GetColour(wxSYS_COLOUR_WINDOW);
|
||||
auto text_clr_str = wxString::Format(wxT("#%02X%02X%02X"), text_clr.Red(), text_clr.Green(), text_clr.Blue());
|
||||
auto bgr_clr_str = wxString::Format(wxT("#%02X%02X%02X"), bgr_clr.Red(), bgr_clr.Green(), bgr_clr.Blue());
|
||||
const int font_size = font.GetPointSize() - 1;
|
||||
int size[] = { font_size, font_size, font_size, font_size, font_size, font_size, font_size };
|
||||
html->SetFonts(font.GetFaceName(), monospace.GetFaceName(), size);
|
||||
html->SetBorders(2);
|
||||
|
||||
// calculate html page size from text
|
||||
int lines = msg.Freq('\n');
|
||||
|
||||
if (msg.Contains("<tr>")) {
|
||||
int pos = 0;
|
||||
while (pos < (int)msg.Len() && pos != wxNOT_FOUND) {
|
||||
pos = msg.find("<tr>", pos + 1);
|
||||
lines+=2;
|
||||
}
|
||||
}
|
||||
int page_height = std::min((font.GetPixelSize().y + 1) * lines, 68 * wxGetApp().em_unit());
|
||||
wxSize page_size(68 * wxGetApp().em_unit(), page_height);
|
||||
|
||||
html->SetMinSize(page_size);
|
||||
|
||||
std::string msg_escaped = xml_escape(msg.ToUTF8().data(), true);
|
||||
boost::replace_all(msg_escaped, "\r\n", "<br>");
|
||||
boost::replace_all(msg_escaped, "\n", "<br>");
|
||||
html->SetPage("<html><body bgcolor=\"" + bgr_clr_str + "\"><font color=\"" + text_clr_str + "\">" + wxString::FromUTF8(msg_escaped.data()) + "</font></body></html>");
|
||||
content_sizer->Add(html, 1, wxEXPAND);
|
||||
}
|
||||
|
||||
// Set info bitmap
|
||||
logo->SetBitmap(create_scaled_bitmap("info", this, 84));
|
||||
|
||||
Fit();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
|
|
@ -114,6 +114,22 @@ public:
|
|||
#endif
|
||||
|
||||
|
||||
// Generic info dialog, used for displaying exceptions
|
||||
class InfoDialog : public MsgDialog
|
||||
{
|
||||
public:
|
||||
InfoDialog(wxWindow *parent, const wxString &title, const wxString &msg);
|
||||
InfoDialog(InfoDialog&&) = delete;
|
||||
InfoDialog(const InfoDialog&) = delete;
|
||||
InfoDialog&operator=(InfoDialog&&) = delete;
|
||||
InfoDialog&operator=(const InfoDialog&) = delete;
|
||||
virtual ~InfoDialog() = default;
|
||||
|
||||
private:
|
||||
wxString msg;
|
||||
};
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -24,7 +24,7 @@ ButtonsListCtrl::ButtonsListCtrl(wxWindow *parent, bool add_mode_buttons/* = fal
|
|||
m_sizer = new wxBoxSizer(wxHORIZONTAL);
|
||||
this->SetSizer(m_sizer);
|
||||
|
||||
m_buttons_sizer = new wxFlexGridSizer(4, m_btn_margin, m_btn_margin);
|
||||
m_buttons_sizer = new wxFlexGridSizer(1, m_btn_margin, m_btn_margin);
|
||||
m_sizer->Add(m_buttons_sizer, 0, wxALIGN_CENTER_VERTICAL | wxLEFT | wxBOTTOM, m_btn_margin);
|
||||
|
||||
if (add_mode_buttons) {
|
||||
|
@ -111,6 +111,7 @@ bool ButtonsListCtrl::InsertPage(size_t n, const wxString& text, bool bSelect/*
|
|||
Slic3r::GUI::wxGetApp().UpdateDarkUI(btn);
|
||||
m_pageButtons.insert(m_pageButtons.begin() + n, btn);
|
||||
m_buttons_sizer->Insert(n, new wxSizerItem(btn));
|
||||
m_buttons_sizer->SetCols(m_buttons_sizer->GetCols() + 1);
|
||||
m_sizer->Layout();
|
||||
return true;
|
||||
}
|
||||
|
|
|
@ -218,6 +218,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
if (m_state == EState::FadingOut) {
|
||||
push_style_color(ImGuiCol_WindowBg, ImGui::GetStyleColorVec4(ImGuiCol_WindowBg), true, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_Text, ImGui::GetStyleColorVec4(ImGuiCol_Text), true, m_current_fade_opacity);
|
||||
push_style_color(ImGuiCol_ButtonHovered, ImGui::GetStyleColorVec4(ImGuiCol_ButtonHovered), true, m_current_fade_opacity);
|
||||
fading_pop = true;
|
||||
}
|
||||
|
||||
|
@ -229,7 +230,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
m_id = m_id_provider.allocate_id();
|
||||
std::string name = "!!Ntfctn" + std::to_string(m_id);
|
||||
|
||||
if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar)) {
|
||||
if (imgui.begin(name, ImGuiWindowFlags_NoCollapse | ImGuiWindowFlags_NoTitleBar | ImGuiWindowFlags_NoMove | ImGuiWindowFlags_NoResize | ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
ImVec2 win_size = ImGui::GetWindowSize();
|
||||
|
||||
render_left_sign(imgui);
|
||||
|
@ -245,7 +246,7 @@ void NotificationManager::PopNotification::render(GLCanvas3D& canvas, float init
|
|||
ImGui::PopStyleColor();
|
||||
|
||||
if (fading_pop)
|
||||
ImGui::PopStyleColor(2);
|
||||
ImGui::PopStyleColor(3);
|
||||
}
|
||||
bool NotificationManager::PopNotification::push_background_color()
|
||||
{
|
||||
|
@ -440,9 +441,7 @@ void NotificationManager::PopNotification::render_hypertext(ImGuiWrapper& imgui,
|
|||
close();
|
||||
}
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(3);
|
||||
|
||||
//hover color
|
||||
ImVec4 orange_color = ImVec4(.99f, .313f, .0f, 1.0f);
|
||||
|
@ -501,11 +500,7 @@ void NotificationManager::PopNotification::render_close_button(ImGuiWrapper& img
|
|||
{
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
|
||||
void NotificationManager::PopNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
|
@ -545,11 +540,7 @@ void NotificationManager::PopNotification::render_minimize_button(ImGuiWrapper&
|
|||
m_multiline = false;
|
||||
}
|
||||
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
m_minimize_b_visible = true;
|
||||
}
|
||||
bool NotificationManager::PopNotification::on_text_click()
|
||||
|
@ -790,11 +781,7 @@ void NotificationManager::ExportFinishedNotification::render_eject_button(ImGuiW
|
|||
wxPostEvent(m_evt_handler, EjectDriveNotificationClickedEvent(EVT_EJECT_DRIVE_NOTIFICAION_CLICKED));
|
||||
close();
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
bool NotificationManager::ExportFinishedNotification::on_text_click()
|
||||
{
|
||||
|
@ -1054,11 +1041,7 @@ void NotificationManager::PrintHostUploadNotification::render_cancel_button(ImGu
|
|||
{
|
||||
wxGetApp().printhost_job_queue().cancel(m_job_id - 1);
|
||||
}
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor();
|
||||
ImGui::PopStyleColor(5);
|
||||
}
|
||||
//------UpdatedItemsInfoNotification-------
|
||||
void NotificationManager::UpdatedItemsInfoNotification::count_spaces()
|
||||
|
@ -1074,10 +1057,39 @@ void NotificationManager::UpdatedItemsInfoNotification::count_spaces()
|
|||
m_window_width_offset = m_left_indentation + m_line_height * 3.f;
|
||||
m_window_width = m_line_height * 25;
|
||||
}
|
||||
void NotificationManager::UpdatedItemsInfoNotification::add_type(InfoItemType type)
|
||||
{
|
||||
std::vector<std::pair<InfoItemType, size_t>>::iterator it = m_types_and_counts.begin();
|
||||
for (; it != m_types_and_counts.end(); ++it) {
|
||||
if ((*it).first == type) {
|
||||
(*it).second++;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (it == m_types_and_counts.end())
|
||||
m_types_and_counts.emplace_back(type, 1);
|
||||
|
||||
std::string text;
|
||||
for (it = m_types_and_counts.begin(); it != m_types_and_counts.end(); ++it) {
|
||||
text += std::to_string((*it).second);
|
||||
text += _L_PLURAL(" Object was loaded with "," Objects were loaded with ", (*it).second).ToUTF8().data();
|
||||
switch ((*it).first) {
|
||||
case InfoItemType::CustomSupports: text += _utf8("custom supports.\n"); break;
|
||||
case InfoItemType::CustomSeam: text += _utf8("custom seam.\n"); break;
|
||||
case InfoItemType::MmuSegmentation: text += _utf8("multimaterial painting.\n"); break;
|
||||
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height.\n"); break;
|
||||
case InfoItemType::Sinking: text += _utf8("Partial sinking.\n"); break;
|
||||
default: BOOST_LOG_TRIVIAL(error) << "Unknown InfoItemType: " << (*it).second; break;
|
||||
}
|
||||
}
|
||||
NotificationData data { get_data().type, get_data().level , get_data().duration, text };
|
||||
update(data);
|
||||
}
|
||||
void NotificationManager::UpdatedItemsInfoNotification::render_left_sign(ImGuiWrapper& imgui)
|
||||
{
|
||||
std::string text;
|
||||
switch (m_info_item_type) {
|
||||
InfoItemType type = (m_types_and_counts.empty() ? InfoItemType::CustomSupports : m_types_and_counts[0].first);
|
||||
switch (type) {
|
||||
case InfoItemType::CustomSupports: text = ImGui::CustomSupportsMarker; break;
|
||||
case InfoItemType::CustomSeam: text = ImGui::CustomSeamMarker; break;
|
||||
case InfoItemType::MmuSegmentation: text = ImGui::MmuSegmentationMarker; break;
|
||||
|
@ -1347,31 +1359,43 @@ void NotificationManager::upload_job_notification_show_error(int id, const std::
|
|||
}
|
||||
}
|
||||
}
|
||||
void NotificationManager::push_hint_notification()
|
||||
void NotificationManager::push_hint_notification(bool open_next)
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::DidYouKnowHint) {
|
||||
(dynamic_cast<HintNotification*>(notification.get()))->open_next();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 300, "" };
|
||||
push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler, open_next), 0);
|
||||
}
|
||||
|
||||
bool NotificationManager::is_hint_notification_open()
|
||||
{
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::DidYouKnowHint)
|
||||
return;
|
||||
return true;
|
||||
}
|
||||
NotificationData data{ NotificationType::DidYouKnowHint, NotificationLevel::RegularNotification, 0, "" };
|
||||
push_notification_data(std::make_unique<NotificationManager::HintNotification>(data, m_id_provider, m_evt_handler), 0);
|
||||
return false;
|
||||
}
|
||||
|
||||
void NotificationManager::push_updated_item_info_notification(InfoItemType type)
|
||||
{
|
||||
std::string text = _utf8("Object(s) were loaded with ");
|
||||
switch (type) {
|
||||
case InfoItemType::CustomSupports: text += _utf8("custom supports."); break;
|
||||
case InfoItemType::CustomSeam: text += _utf8("custom seam."); break;
|
||||
case InfoItemType::MmuSegmentation: text += _utf8("MMU segmentation."); break;
|
||||
case InfoItemType::VariableLayerHeight: text += _utf8("variable layer height."); break;
|
||||
case InfoItemType::Sinking: text = _utf8("Partially sinking object(s) were loaded."); break;
|
||||
default: text.clear(); break;
|
||||
for (std::unique_ptr<PopNotification>& notification : m_pop_notifications) {
|
||||
if (notification->get_type() == NotificationType::UpdatedItemsInfo) {
|
||||
(dynamic_cast<UpdatedItemsInfoNotification*>(notification.get()))->add_type(type);
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!text.empty()) {
|
||||
NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 10, text };
|
||||
push_notification_data(std::make_unique<NotificationManager::UpdatedItemsInfoNotification>(data, m_id_provider, m_evt_handler, type), 0);
|
||||
|
||||
NotificationData data{ NotificationType::UpdatedItemsInfo, NotificationLevel::RegularNotification, 5, "" };
|
||||
auto notification = std::make_unique<NotificationManager::UpdatedItemsInfoNotification>(data, m_id_provider, m_evt_handler, type);
|
||||
if (push_notification_data(std::move(notification), 0)) {
|
||||
(dynamic_cast<UpdatedItemsInfoNotification*>(m_pop_notifications.back().get()))->add_type(type);
|
||||
}
|
||||
|
||||
}
|
||||
bool NotificationManager::push_notification_data(const NotificationData& notification_data, int timestamp)
|
||||
{
|
||||
|
|
|
@ -169,7 +169,8 @@ public:
|
|||
void upload_job_notification_show_canceled(int id, const std::string& filename, const std::string& host);
|
||||
void upload_job_notification_show_error(int id, const std::string& filename, const std::string& host);
|
||||
// Hint (did you know) notification
|
||||
void push_hint_notification();
|
||||
void push_hint_notification(bool open_next);
|
||||
bool is_hint_notification_open();
|
||||
void push_updated_item_info_notification(InfoItemType type);
|
||||
// Close old notification ExportFinished.
|
||||
void new_export_began(bool on_removable);
|
||||
|
@ -498,13 +499,14 @@ private:
|
|||
public:
|
||||
UpdatedItemsInfoNotification(const NotificationData& n, NotificationIDProvider& id_provider, wxEvtHandler* evt_handler, InfoItemType info_item_type)
|
||||
: PopNotification(n, id_provider, evt_handler)
|
||||
, m_info_item_type(info_item_type)
|
||||
{
|
||||
//m_types_and_counts.emplace_back(info_item_type, 1);
|
||||
}
|
||||
void count_spaces() override;
|
||||
void add_type(InfoItemType type);
|
||||
protected:
|
||||
void render_left_sign(ImGuiWrapper& imgui) override;
|
||||
InfoItemType m_info_item_type;
|
||||
std::vector<std::pair<InfoItemType, size_t>> m_types_and_counts;
|
||||
};
|
||||
|
||||
// in HintNotification.hpp
|
||||
|
|
|
@ -37,7 +37,21 @@ void ObjectDataViewModelNode::init_container()
|
|||
static constexpr char LayerRootIcon[] = "edit_layers_all";
|
||||
static constexpr char LayerIcon[] = "edit_layers_some";
|
||||
static constexpr char WarningIcon[] = "exclamation";
|
||||
static constexpr char InfoIcon[] = "info";
|
||||
static constexpr char InfoIcon[] = "objlist_info";
|
||||
|
||||
struct InfoItemAtributes {
|
||||
std::string name;
|
||||
std::string bmp_name;
|
||||
};
|
||||
|
||||
const std::map<InfoItemType, InfoItemAtributes> INFO_ITEMS{
|
||||
// info_item Type info_item Name info_item BitmapName
|
||||
{ InfoItemType::CustomSupports, {L("Paint-on supports"), "fdm_supports" }, },
|
||||
{ InfoItemType::CustomSeam, {L("Paint-on seam"), "seam" }, },
|
||||
{ InfoItemType::MmuSegmentation, {L("Multimaterial painting"), "mmu_segmentation"}, },
|
||||
{ InfoItemType::Sinking, {L("Sinking"), "support_blocker"}, },
|
||||
{ InfoItemType::VariableLayerHeight, {L("Variable layer height"), "layers"}, },
|
||||
};
|
||||
|
||||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent,
|
||||
const wxString& sub_obj_name,
|
||||
|
@ -60,14 +74,10 @@ ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* pare
|
|||
ObjectDataViewModelNode::ObjectDataViewModelNode(ObjectDataViewModelNode* parent, const InfoItemType info_type) :
|
||||
m_parent(parent),
|
||||
m_type(itInfo),
|
||||
m_extruder(wxEmptyString)
|
||||
m_info_item_type(info_type),
|
||||
m_extruder(wxEmptyString),
|
||||
m_name(_(INFO_ITEMS.at(info_type).name))
|
||||
{
|
||||
m_name = info_type == InfoItemType::CustomSupports ? _L("Paint-on supports") :
|
||||
info_type == InfoItemType::CustomSeam ? _L("Paint-on seam") :
|
||||
info_type == InfoItemType::MmuSegmentation ? _L("Paint-on segmentation") :
|
||||
info_type == InfoItemType::Sinking ? _L("Sinking") :
|
||||
_L("Variable layer height");
|
||||
m_info_item_type = info_type;
|
||||
}
|
||||
|
||||
|
||||
|
@ -307,7 +317,8 @@ ObjectDataViewModel::ObjectDataViewModel()
|
|||
|
||||
m_volume_bmps = MenuFactory::get_volume_bitmaps();
|
||||
m_warning_bmp = create_scaled_bitmap(WarningIcon);
|
||||
m_info_bmp = create_scaled_bitmap(InfoIcon);
|
||||
for (auto item : INFO_ITEMS)
|
||||
m_info_bmps[item.first] = create_scaled_bitmap(item.second.bmp_name);
|
||||
}
|
||||
|
||||
ObjectDataViewModel::~ObjectDataViewModel()
|
||||
|
@ -402,7 +413,7 @@ wxDataViewItem ObjectDataViewModel::AddInfoChild(const wxDataViewItem &parent_it
|
|||
}
|
||||
|
||||
root->Insert(node, idx+1);
|
||||
node->SetBitmap(m_info_bmp);
|
||||
node->SetBitmap(m_info_bmps.at(info_type));
|
||||
// notify control
|
||||
const wxDataViewItem child((void*)node);
|
||||
ItemAdded(parent_item, child);
|
||||
|
@ -1494,7 +1505,17 @@ void ObjectDataViewModel::GetAllChildren(const wxDataViewItem &parent, wxDataVie
|
|||
}
|
||||
}
|
||||
|
||||
ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
bool ObjectDataViewModel::HasInfoItem(InfoItemType type) const
|
||||
{
|
||||
for (ObjectDataViewModelNode* obj_node : m_objects)
|
||||
for (size_t j = 0; j < obj_node->GetChildCount(); j++)
|
||||
if (obj_node->GetNthChild(j)->GetInfoItemType() == type)
|
||||
return true;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ItemType ObjectDataViewModel::GetItemType(const wxDataViewItem &item) const
|
||||
{
|
||||
if (!item.IsOk())
|
||||
return itUndef;
|
||||
|
|
|
@ -3,6 +3,7 @@
|
|||
|
||||
#include <wx/dataview.h>
|
||||
#include <vector>
|
||||
#include <map>
|
||||
|
||||
#include "ExtraRenderers.hpp"
|
||||
|
||||
|
@ -251,8 +252,8 @@ class ObjectDataViewModel :public wxDataViewModel
|
|||
{
|
||||
std::vector<ObjectDataViewModelNode*> m_objects;
|
||||
std::vector<wxBitmap> m_volume_bmps;
|
||||
std::map<InfoItemType, wxBitmap> m_info_bmps;
|
||||
wxBitmap m_warning_bmp;
|
||||
wxBitmap m_info_bmp;
|
||||
|
||||
wxDataViewCtrl* m_ctrl { nullptr };
|
||||
|
||||
|
@ -345,6 +346,7 @@ public:
|
|||
// Is the container just a header or an item with all columns
|
||||
// In our case it is an item with all columns
|
||||
bool HasContainerColumns(const wxDataViewItem& WXUNUSED(item)) const override { return true; }
|
||||
bool HasInfoItem(InfoItemType type) const;
|
||||
|
||||
ItemType GetItemType(const wxDataViewItem &item) const;
|
||||
InfoItemType GetInfoItemType(const wxDataViewItem &item) const;
|
||||
|
|
|
@ -88,11 +88,6 @@ const t_field& OptionsGroup::build_field(const t_config_option_key& id, const Co
|
|||
if (!m_disabled)
|
||||
this->on_kill_focus(opt_id);
|
||||
};
|
||||
field->m_on_set_focus = [this](const std::string& opt_id) {
|
||||
//! This function will be called from Field.
|
||||
if (!m_disabled)
|
||||
this->on_set_focus(opt_id);
|
||||
};
|
||||
field->m_parent = parent();
|
||||
|
||||
field->m_back_to_initial_value = [this](std::string opt_id) {
|
||||
|
@ -514,12 +509,6 @@ void OptionsGroup::clear_fields_except_of(const std::vector<std::string> left_fi
|
|||
}
|
||||
}
|
||||
|
||||
void OptionsGroup::on_set_focus(const std::string& opt_key)
|
||||
{
|
||||
if (m_set_focus != nullptr)
|
||||
m_set_focus(opt_key);
|
||||
}
|
||||
|
||||
void OptionsGroup::on_change_OG(const t_config_option_key& opt_id, const boost::any& value) {
|
||||
if (m_on_change != nullptr)
|
||||
m_on_change(opt_id, value);
|
||||
|
|
|
@ -100,7 +100,6 @@ public:
|
|||
// To be called when the field loses focus, to assign a new initial value to the field.
|
||||
// Used by the relative position / rotation / scale manipulation fields of the Object Manipulation UI.
|
||||
t_kill_focus m_fill_empty_value { nullptr };
|
||||
t_kill_focus m_set_focus { nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_initial_config{ nullptr };
|
||||
std::function<DynamicPrintConfig()> m_get_sys_config{ nullptr };
|
||||
std::function<bool()> have_sys_config{ nullptr };
|
||||
|
@ -208,7 +207,6 @@ protected:
|
|||
const t_field& build_field(const Option& opt);
|
||||
|
||||
virtual void on_kill_focus(const std::string& opt_key) {};
|
||||
virtual void on_set_focus(const std::string& opt_key);
|
||||
virtual void on_change_OG(const t_config_option_key& opt_id, const boost::any& value);
|
||||
virtual void back_to_initial_value(const std::string& opt_key) {}
|
||||
virtual void back_to_sys_value(const std::string& opt_key) {}
|
||||
|
|
|
@ -1576,20 +1576,19 @@ struct Plater::priv
|
|||
|
||||
bool is_project_dirty() const { return dirty_state.is_dirty(); }
|
||||
void update_project_dirty_from_presets() { dirty_state.update_from_presets(); }
|
||||
bool save_project_if_dirty() {
|
||||
int save_project_if_dirty() {
|
||||
int res = wxID_NO;
|
||||
if (dirty_state.is_dirty()) {
|
||||
MainFrame* mainframe = wxGetApp().mainframe;
|
||||
if (mainframe->can_save_as()) {
|
||||
//wxMessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
MessageDialog dlg(mainframe, _L("Do you want to save the changes to the current project ?"), wxString(SLIC3R_APP_NAME), wxYES_NO | wxCANCEL);
|
||||
int res = dlg.ShowModal();
|
||||
res = dlg.ShowModal();
|
||||
if (res == wxID_YES)
|
||||
mainframe->save_project_as(wxGetApp().plater()->get_project_filename());
|
||||
else if (res == wxID_CANCEL)
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
return res;
|
||||
}
|
||||
void reset_project_dirty_after_save() { dirty_state.reset_after_save(); }
|
||||
void reset_project_dirty_initial_presets() { dirty_state.reset_initial_presets(); }
|
||||
|
@ -2262,12 +2261,8 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
// and place the loaded config over the base.
|
||||
config += std::move(config_loaded);
|
||||
}
|
||||
if (! config_substitutions.empty()) {
|
||||
// TODO:
|
||||
show_error(nullptr, GUI::format(_L("Loading profiles found following incompatibilities."
|
||||
" To recover these files, incompatible values were changed to default values."
|
||||
" But data in files won't be changed until you save them in PrusaSlicer.")));
|
||||
}
|
||||
if (! config_substitutions.empty())
|
||||
show_substitutions_info(config_substitutions.substitutions, filename.string());
|
||||
|
||||
this->model.custom_gcode_per_print_z = model.custom_gcode_per_print_z;
|
||||
}
|
||||
|
@ -2342,6 +2337,10 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
|
|||
if (obj->name.empty())
|
||||
obj->name = fs::path(obj->input_file).filename().string();
|
||||
}
|
||||
} catch (const ConfigurationError &e) {
|
||||
std::string message = GUI::format(_L("Failed loading file \"%1%\" due to an invalid configuration."), filename.string()) + "\n\n" + e.what();
|
||||
GUI::show_error(q, message);
|
||||
continue;
|
||||
} catch (const std::exception &e) {
|
||||
GUI::show_error(q, e.what());
|
||||
continue;
|
||||
|
@ -2564,6 +2563,10 @@ std::vector<size_t> Plater::priv::load_model_objects(const ModelObjectPtrs& mode
|
|||
_L("Object too large?"));
|
||||
}
|
||||
|
||||
// Now ObjectList uses GLCanvas3D::is_object_sinkin() to show/hide "Sinking" InfoItem,
|
||||
// so 3D-scene should be updated before object additing to the ObjectList
|
||||
this->view3D->reload_scene(false, (unsigned int)UpdateParams::FORCE_FULL_SCREEN_REFRESH);
|
||||
|
||||
for (const size_t idx : obj_idxs) {
|
||||
wxGetApp().obj_list()->add_object_to_list(idx);
|
||||
}
|
||||
|
@ -3500,6 +3503,8 @@ void Plater::priv::fix_through_netfabb(const int obj_idx, const int vol_idx/* =
|
|||
ModelObject* mo = model.objects[obj_idx];
|
||||
fix_model_by_win10_sdk_gui(*mo, vol_idx);
|
||||
q->changed_mesh(obj_idx);
|
||||
// workaround to fix the issue, when PrusaSlicer lose a focus after model fixing
|
||||
q->SetFocus();
|
||||
}
|
||||
|
||||
void Plater::priv::set_current_panel(wxPanel* panel)
|
||||
|
@ -3918,6 +3923,10 @@ void Plater::priv::on_right_click(RBtnEvent& evt)
|
|||
if (evt.data.second)
|
||||
return;
|
||||
|
||||
// Each context menu respects to the selected item in ObjectList,
|
||||
// so this selection should be updated before menu creation
|
||||
wxGetApp().obj_list()->update_selections();
|
||||
|
||||
if (printer_technology == ptSLA)
|
||||
menu = menus.sla_object_menu();
|
||||
else {
|
||||
|
@ -4639,7 +4648,7 @@ Plater::Plater(wxWindow *parent, MainFrame *main_frame)
|
|||
|
||||
bool Plater::is_project_dirty() const { return p->is_project_dirty(); }
|
||||
void Plater::update_project_dirty_from_presets() { p->update_project_dirty_from_presets(); }
|
||||
bool Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
|
||||
int Plater::save_project_if_dirty() { return p->save_project_if_dirty(); }
|
||||
void Plater::reset_project_dirty_after_save() { p->reset_project_dirty_after_save(); }
|
||||
void Plater::reset_project_dirty_initial_presets() { p->reset_project_dirty_initial_presets(); }
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
|
@ -5294,13 +5303,14 @@ void Plater::export_gcode(bool prefer_removable)
|
|||
|
||||
fs::path output_path;
|
||||
{
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 file as:"),
|
||||
std::string ext = default_output_file.extension().string();
|
||||
wxFileDialog dlg(this, (printer_technology() == ptFFF) ? _L("Save G-code file as:") : _L("Save SL1 / SL1S file as:"),
|
||||
start_dir,
|
||||
from_path(default_output_file.filename()),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : FT_PNGZIP, default_output_file.extension().string()),
|
||||
GUI::file_wildcards((printer_technology() == ptFFF) ? FT_GCODE : boost::iequals(ext, ".sl1s") ? FT_SL1S : FT_SL1, ext),
|
||||
wxFD_SAVE | wxFD_OVERWRITE_PROMPT
|
||||
);
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
if (dlg.ShowModal() == wxID_OK)
|
||||
output_path = into_path(dlg.GetPath());
|
||||
}
|
||||
|
||||
|
@ -6126,7 +6136,8 @@ void Plater::clear_before_change_mesh(int obj_idx)
|
|||
get_notification_manager()->push_notification(
|
||||
NotificationType::CustomSupportsAndSeamRemovedAfterRepair,
|
||||
NotificationManager::NotificationLevel::RegularNotification,
|
||||
_u8L("Custom supports and seams were removed after repairing the mesh."));
|
||||
_u8L("Custom supports, seams and multimaterial painting were "
|
||||
"removed after repairing the mesh."));
|
||||
// _u8L("Undo the repair"),
|
||||
// [this, snapshot_time](wxEvtHandler*){
|
||||
// // Make sure the snapshot is still available and that
|
||||
|
|
|
@ -140,7 +140,7 @@ public:
|
|||
|
||||
bool is_project_dirty() const;
|
||||
void update_project_dirty_from_presets();
|
||||
bool save_project_if_dirty();
|
||||
int save_project_if_dirty();
|
||||
void reset_project_dirty_after_save();
|
||||
void reset_project_dirty_initial_presets();
|
||||
#if ENABLE_PROJECT_DIRTY_STATE_DEBUG_WINDOW
|
||||
|
|
|
@ -342,7 +342,7 @@ void PreferencesDialog::build(size_t selected_tab)
|
|||
m_optgroup_gui->append_single_option_line(option);
|
||||
#endif
|
||||
|
||||
def.label = L("Show \"Did you know\" hints after start");
|
||||
def.label = L("Show \"Tip of the day\" notification after start");
|
||||
def.type = coBool;
|
||||
def.tooltip = L("If enabled, useful hints are displayed at startup.");
|
||||
def.set_default_value(new ConfigOptionBool{ app_config->get("show_hints") == "1" });
|
||||
|
|
|
@ -2208,6 +2208,7 @@ void TabPrinter::build_fff()
|
|||
def.label = L("Extruders");
|
||||
def.tooltip = L("Number of extruders of the printer.");
|
||||
def.min = 1;
|
||||
def.max = 256;
|
||||
def.mode = comExpert;
|
||||
Option option(def, "extruders_count");
|
||||
optgroup->append_single_option_line(option);
|
||||
|
|
|
@ -85,8 +85,11 @@ bool MsgUpdateSlic3r::disable_version_check() const
|
|||
|
||||
// MsgUpdateConfig
|
||||
|
||||
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
|
||||
MsgDialog(nullptr, _(L("Configuration update")), _(L("Configuration update is available")), wxID_NONE)
|
||||
MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard/* = false*/) :
|
||||
MsgDialog(nullptr, force_before_wizard ? _L("Opening Configuration Wizard") : _L("Configuration update"),
|
||||
force_before_wizard ? _L("PrusaSlicer is not using the newest configuration available.\n"
|
||||
"Configuration Wizard may not offer the latest printers, filaments and SLA materials to be installed. ") :
|
||||
_L("Configuration update is available"), wxID_NONE)
|
||||
{
|
||||
auto *text = new wxStaticText(this, wxID_ANY, _(L(
|
||||
"Would you like to install it?\n\n"
|
||||
|
@ -130,11 +133,17 @@ MsgUpdateConfig::MsgUpdateConfig(const std::vector<Update> &updates) :
|
|||
content_sizer->Add(versions);
|
||||
content_sizer->AddSpacer(2*VERT_SPACING);
|
||||
|
||||
auto *btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
btn_sizer->Add(btn_cancel);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
auto *btn_ok = new wxButton(this, wxID_OK);
|
||||
auto* btn_ok = new wxButton(this, wxID_OK, force_before_wizard ? _L("Install") : "OK");
|
||||
btn_sizer->Add(btn_ok);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
if (force_before_wizard) {
|
||||
auto* btn_no_install = new wxButton(this, wxID_ANY, "Don't install");
|
||||
btn_no_install->Bind(wxEVT_BUTTON, [this](wxEvent&) { this->EndModal(wxID_CLOSE); });
|
||||
btn_sizer->Add(btn_no_install);
|
||||
btn_sizer->AddSpacer(HORIZ_SPACING);
|
||||
}
|
||||
auto* btn_cancel = new wxButton(this, wxID_CANCEL);
|
||||
btn_sizer->Add(btn_cancel);
|
||||
btn_ok->SetFocus();
|
||||
|
||||
wxGetApp().UpdateDlgDarkUI(this);
|
||||
|
|
|
@ -54,7 +54,8 @@ public:
|
|||
{}
|
||||
};
|
||||
|
||||
MsgUpdateConfig(const std::vector<Update> &updates);
|
||||
// force_before_wizard - indicates that check of updated is forced before ConfigWizard opening
|
||||
MsgUpdateConfig(const std::vector<Update> &updates, bool force_before_wizard = false);
|
||||
MsgUpdateConfig(MsgUpdateConfig &&) = delete;
|
||||
MsgUpdateConfig(const MsgUpdateConfig &) = delete;
|
||||
MsgUpdateConfig &operator=(MsgUpdateConfig &&) = delete;
|
||||
|
|
|
@ -36,8 +36,6 @@
|
|||
#include "../GUI/GUI.hpp"
|
||||
#include "../GUI/I18N.hpp"
|
||||
#include "../GUI/MsgDialog.hpp"
|
||||
#include "../GUI/GUI_App.hpp"
|
||||
#include "../GUI/Mainframe.hpp"
|
||||
|
||||
#include <wx/msgdlg.h>
|
||||
#include <wx/progdlg.h>
|
||||
|
@ -343,7 +341,7 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
wxProgressDialog progress_dialog(
|
||||
_L("Model fixing"),
|
||||
_L("Exporting model") + "...",
|
||||
100, GUI::wxGetApp().mainframe, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT);
|
||||
100, nullptr, wxPD_AUTO_HIDE | wxPD_APP_MODAL | wxPD_CAN_ABORT); // ! parent of the wxProgressDialog should be nullptr to avoid flickering during the model fixing
|
||||
// Executing the calculation in a background thread, so that the COM context could be created with its own threading model.
|
||||
// (It seems like wxWidgets initialize the COM contex as single threaded and we need a multi-threaded context).
|
||||
bool success = false;
|
||||
|
@ -364,9 +362,17 @@ void fix_model_by_win10_sdk_gui(ModelObject &model_object, int volume_idx)
|
|||
boost::filesystem::path path_src = boost::filesystem::temp_directory_path() / boost::filesystem::unique_path();
|
||||
path_src += ".3mf";
|
||||
Model model;
|
||||
ModelObject *model_object = model.add_object();
|
||||
model_object->add_volume(*volumes[ivolume]);
|
||||
model_object->add_instance();
|
||||
ModelObject *mo = model.add_object();
|
||||
mo->add_volume(*volumes[ivolume]);
|
||||
|
||||
// We are about to save a 3mf, fix it by netfabb and load the fixed 3mf back.
|
||||
// store_3mf currently bakes the volume transformation into the mesh itself.
|
||||
// If we then loaded the repaired 3mf and pushed the mesh into the original ModelVolume
|
||||
// (which remembers the matrix the whole time), the transformation would be used twice.
|
||||
// We will therefore set the volume transform on the dummy ModelVolume to identity.
|
||||
mo->volumes.back()->set_transformation(Geometry::Transformation());
|
||||
|
||||
mo->add_instance();
|
||||
if (!Slic3r::store_3mf(path_src.string().c_str(), &model, nullptr, false, nullptr, false)) {
|
||||
boost::filesystem::remove(path_src);
|
||||
throw Slic3r::RuntimeError(L("Export of a temporary 3mf file failed"));
|
||||
|
|
|
@ -19,6 +19,7 @@ static HexFile::DeviceKind parse_device_kind(const std::string &str)
|
|||
else if (str == "mk3") { return HexFile::DEV_MK3; }
|
||||
else if (str == "mm-control") { return HexFile::DEV_MM_CONTROL; }
|
||||
else if (str == "cw1") { return HexFile::DEV_CW1; }
|
||||
else if (str == "cw1s") { return HexFile::DEV_CW1S; }
|
||||
else { return HexFile::DEV_GENERIC; }
|
||||
}
|
||||
|
||||
|
|
|
@ -17,6 +17,7 @@ struct HexFile
|
|||
DEV_MK3,
|
||||
DEV_MM_CONTROL,
|
||||
DEV_CW1,
|
||||
DEV_CW1S,
|
||||
};
|
||||
|
||||
boost::filesystem::path path;
|
||||
|
|
|
@ -182,7 +182,7 @@ const char* SL1Host::get_name() const { return "SL1Host"; }
|
|||
|
||||
wxString SL1Host::get_test_ok_msg () const
|
||||
{
|
||||
return _(L("Connection to Prusa SL1 works correctly."));
|
||||
return _(L("Connection to Prusa SL1 / SL1S works correctly."));
|
||||
}
|
||||
|
||||
wxString SL1Host::get_test_failed_msg (wxString &msg) const
|
||||
|
|
|
@ -65,6 +65,10 @@ void copy_file_fix(const fs::path &source, const fs::path &target)
|
|||
_L("Copying of file %1% to %2% failed: %3%"),
|
||||
source, target, error_message));
|
||||
}
|
||||
// Permissions should be copied from the source file by copy_file(). We are not sure about the source
|
||||
// permissions, let's rewrite them with 644.
|
||||
static constexpr const auto perms = fs::owner_read | fs::owner_write | fs::group_read | fs::others_read;
|
||||
fs::permissions(target, perms);
|
||||
}
|
||||
|
||||
struct Update
|
||||
|
@ -167,7 +171,7 @@ struct PresetUpdater::priv
|
|||
|
||||
void check_install_indices() const;
|
||||
Updates get_config_updates(const Semver& old_slic3r_version) const;
|
||||
void perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
bool perform_updates(Updates &&updates, bool snapshot = true) const;
|
||||
void set_waiting_updates(Updates u);
|
||||
};
|
||||
|
||||
|
@ -580,12 +584,14 @@ Updates PresetUpdater::priv::get_config_updates(const Semver &old_slic3r_version
|
|||
return updates;
|
||||
}
|
||||
|
||||
void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
|
||||
bool PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) const
|
||||
{
|
||||
if (updates.incompats.size() > 0) {
|
||||
if (snapshot) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
||||
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE);
|
||||
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_DOWNGRADE, "",
|
||||
_u8L("Continue and install configuration updates?")))
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% incompatible bundles", updates.incompats.size());
|
||||
|
@ -600,7 +606,9 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
|
||||
if (snapshot) {
|
||||
BOOST_LOG_TRIVIAL(info) << "Taking a snapshot...";
|
||||
SnapshotDB::singleton().take_snapshot(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE);
|
||||
if (! GUI::Config::take_config_snapshot_cancel_on_error(*GUI::wxGetApp().app_config, Snapshot::SNAPSHOT_UPGRADE, "",
|
||||
_u8L("Continue and install configuration updates?")))
|
||||
return false;
|
||||
}
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Performing %1% updates", updates.updates.size());
|
||||
|
@ -611,7 +619,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
update.install();
|
||||
|
||||
PresetBundle bundle;
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem);
|
||||
// Throw when parsing invalid configuration. Only valid configuration is supposed to be provided over the air.
|
||||
bundle.load_configbundle(update.source.string(), PresetBundle::LoadConfigBundleAttribute::LoadSystem, ForwardCompatibilitySubstitutionRule::Disable);
|
||||
|
||||
BOOST_LOG_TRIVIAL(info) << format("Deleting %1% conflicting presets", bundle.prints.size() + bundle.filaments.size() + bundle.printers.size());
|
||||
|
||||
|
@ -643,6 +652,8 @@ void PresetUpdater::priv::perform_updates(Updates &&updates, bool snapshot) cons
|
|||
for (const auto &name : bundle.obsolete_presets.printers) { obsolete_remover("printer", name); }
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void PresetUpdater::priv::set_waiting_updates(Updates u)
|
||||
|
@ -715,12 +726,14 @@ static void reload_configs_update_gui()
|
|||
auto* app_config = GUI::wxGetApp().app_config;
|
||||
// System profiles should not trigger any substitutions, user profiles may trigger substitutions, but these substitutions
|
||||
// were already presented to the user on application start up. Just do substitutions now and keep quiet about it.
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilent);
|
||||
// However throw on substitutions in system profiles, those shall never happen with system profiles installed over the air.
|
||||
GUI::wxGetApp().preset_bundle->load_presets(*app_config, ForwardCompatibilitySubstitutionRule::EnableSilentDisableSystem);
|
||||
GUI::wxGetApp().load_current_presets();
|
||||
GUI::wxGetApp().plater()->set_bed_shape();
|
||||
GUI::wxGetApp().update_wizard_from_config();
|
||||
}
|
||||
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, bool no_notification) const
|
||||
PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3r_version, UpdateParams params) const
|
||||
{
|
||||
if (! p->enabled_config_update) { return R_NOOP; }
|
||||
|
||||
|
@ -754,11 +767,9 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
|
||||
// This effectively removes the incompatible bundles:
|
||||
// (snapshot is taken beforehand)
|
||||
p->perform_updates(std::move(updates));
|
||||
|
||||
if (!GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT)) {
|
||||
if (! p->perform_updates(std::move(updates)) ||
|
||||
! GUI::wxGetApp().run_wizard(GUI::ConfigWizard::RR_DATA_INCOMPAT))
|
||||
return R_INCOMPAT_EXIT;
|
||||
}
|
||||
|
||||
return R_INCOMPAT_CONFIGURED;
|
||||
}
|
||||
|
@ -792,7 +803,8 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(info) << "User wants to update...";
|
||||
p->perform_updates(std::move(updates));
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
return R_INCOMPAT_EXIT;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
|
@ -803,7 +815,11 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
}
|
||||
|
||||
// regular update
|
||||
if (no_notification) {
|
||||
if (params == UpdateParams::SHOW_NOTIFICATION) {
|
||||
p->set_waiting_updates(updates);
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << format("Update of %1% bundles available. Asking for confirmation ...", p->waiting_updates.updates.size());
|
||||
|
||||
std::vector<GUI::MsgUpdateConfig::Update> updates_msg;
|
||||
|
@ -812,22 +828,22 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
updates_msg.emplace_back(update.vendor, update.version.config_version, update.version.comment, std::move(changelog_url));
|
||||
}
|
||||
|
||||
GUI::MsgUpdateConfig dlg(updates_msg);
|
||||
GUI::MsgUpdateConfig dlg(updates_msg, params == UpdateParams::FORCED_BEFORE_WIZARD);
|
||||
|
||||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(updates));
|
||||
if (! p->perform_updates(std::move(updates)))
|
||||
return R_ALL_CANCELED;
|
||||
reload_configs_update_gui();
|
||||
return R_UPDATE_INSTALLED;
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
||||
if (params == UpdateParams::FORCED_BEFORE_WIZARD && res == wxID_CANCEL)
|
||||
return R_ALL_CANCELED;
|
||||
return R_UPDATE_REJECT;
|
||||
}
|
||||
} else {
|
||||
p->set_waiting_updates(updates);
|
||||
GUI::wxGetApp().plater()->get_notification_manager()->push_notification(GUI::NotificationType::PresetUpdateAvailable);
|
||||
}
|
||||
|
||||
// MsgUpdateConfig will show after the notificaation is clicked
|
||||
|
@ -838,7 +854,7 @@ PresetUpdater::UpdateResult PresetUpdater::config_update(const Semver& old_slic3
|
|||
return R_NOOP;
|
||||
}
|
||||
|
||||
void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
|
||||
bool PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot) const
|
||||
{
|
||||
Updates updates;
|
||||
|
||||
|
@ -850,7 +866,7 @@ void PresetUpdater::install_bundles_rsrc(std::vector<std::string> bundles, bool
|
|||
updates.updates.emplace_back(std::move(path_in_rsrc), std::move(path_in_vendors), Version(), "", "");
|
||||
}
|
||||
|
||||
p->perform_updates(std::move(updates), snapshot);
|
||||
return p->perform_updates(std::move(updates), snapshot);
|
||||
}
|
||||
|
||||
void PresetUpdater::on_update_notification_confirm()
|
||||
|
@ -870,16 +886,14 @@ void PresetUpdater::on_update_notification_confirm()
|
|||
const auto res = dlg.ShowModal();
|
||||
if (res == wxID_OK) {
|
||||
BOOST_LOG_TRIVIAL(debug) << "User agreed to perform the update";
|
||||
p->perform_updates(std::move(p->waiting_updates));
|
||||
reload_configs_update_gui();
|
||||
p->has_waiting_updates = false;
|
||||
//return R_UPDATE_INSTALLED;
|
||||
if (p->perform_updates(std::move(p->waiting_updates))) {
|
||||
reload_configs_update_gui();
|
||||
p->has_waiting_updates = false;
|
||||
}
|
||||
}
|
||||
else {
|
||||
BOOST_LOG_TRIVIAL(info) << "User refused the update";
|
||||
//return R_UPDATE_REJECT;
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -35,18 +35,24 @@ public:
|
|||
R_INCOMPAT_CONFIGURED,
|
||||
R_UPDATE_INSTALLED,
|
||||
R_UPDATE_REJECT,
|
||||
R_UPDATE_NOTIFICATION
|
||||
R_UPDATE_NOTIFICATION,
|
||||
R_ALL_CANCELED
|
||||
};
|
||||
|
||||
enum class UpdateParams {
|
||||
SHOW_TEXT_BOX, // force modal textbox
|
||||
SHOW_NOTIFICATION, // only shows notification
|
||||
FORCED_BEFORE_WIZARD // indicates that check of updated is forced before ConfigWizard opening
|
||||
};
|
||||
|
||||
// If updating is enabled, check if updates are available in cache, if so, ask about installation.
|
||||
// A false return value implies Slic3r should exit due to incompatibility of configuration.
|
||||
// Providing old slic3r version upgrade profiles on upgrade of an application even in case
|
||||
// that the config index installed from the Internet is equal to the index contained in the installation package.
|
||||
// no_notification = force modal textbox, otherwise some cases only shows notification
|
||||
UpdateResult config_update(const Semver &old_slic3r_version, bool no_notification) const;
|
||||
UpdateResult config_update(const Semver &old_slic3r_version, UpdateParams params) const;
|
||||
|
||||
// "Update" a list of bundles from resources (behaves like an online update).
|
||||
void install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
bool install_bundles_rsrc(std::vector<std::string> bundles, bool snapshot = true) const;
|
||||
|
||||
void on_update_notification_confirm();
|
||||
private:
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue