Merge branch 'master' into fs_dir_per_glyph

This commit is contained in:
Filip Sykala - NTB T15p 2023-05-26 16:50:39 +02:00
commit 5d37932b4d
57 changed files with 24443 additions and 22734 deletions

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -98,7 +98,7 @@ src/slic3r/Utils/Process.cpp
src/slic3r/Utils/Repetier.cpp
src/slic3r/Config/Snapshot.cpp
src/libslic3r/GCode.cpp
src/libslic3r/ExtrusionEntity.cpp
src/libslic3r/ExtrusionRole.cpp
src/libslic3r/Flow.cpp
src/libslic3r/Format/3mf.cpp
src/libslic3r/Format/AMF.cpp
@ -111,6 +111,7 @@ src/libslic3r/SLA/Pad.cpp
src/libslic3r/SLA/Hollowing.cpp
src/libslic3r/SLAPrint.cpp
src/libslic3r/SLAPrintSteps.cpp
src/libslic3r/Utils.cpp
src/libslic3r/PrintBase.cpp
src/libslic3r/PrintConfig.cpp
src/libslic3r/Zipper.cpp

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -568,9 +568,9 @@ namespace ImGui
// - Note that in C++ a 'float v[X]' function argument is the _same_ as 'float* v', the array syntax is just a way to document the number of elements that are expected to be accessible.
// - You can pass the address of a first float element out of a contiguous structure, e.g. &myvector.x
IMGUI_API bool ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);
IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0);
IMGUI_API bool ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const char* current_label = NULL, const char* original_label = NULL);
IMGUI_API bool ColorPicker3(const char* label, float col[3], ImGuiColorEditFlags flags = 0);
IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL);
IMGUI_API bool ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags = 0, const float* ref_col = NULL, const char* current_label = NULL, const char* original_label = NULL);
IMGUI_API bool ColorButton(const char* desc_id, const ImVec4& col, ImGuiColorEditFlags flags = 0, ImVec2 size = ImVec2(0, 0)); // display a color square/button, hover for details, return true when pressed.
IMGUI_API void SetColorEditOptions(ImGuiColorEditFlags flags); // initialize current options (generally on application startup) if you want to select a default format, picker type, etc. User will be able to change many settings, unless you pass the _NoOptions flag to your calls.

View File

@ -4689,7 +4689,7 @@ bool ImGui::ColorEdit3(const char* label, float col[3], ImGuiColorEditFlags flag
// Edit colors components (each component in 0.0f..1.0f range).
// See enum ImGuiColorEditFlags_ for available options. e.g. Only access 3 floats if ImGuiColorEditFlags_NoAlpha flag is set.
// With typical options: Left-click on color square to open color picker. Right-click to open option menu. CTRL-Click over input fields to edit them and TAB to go to next item.
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags)
bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flags, const char* current_label/* = NULL*/, const char* original_label/* = NULL*/)
{
ImGuiWindow* window = GetCurrentWindow();
if (window->SkipItems)
@ -4859,7 +4859,7 @@ bool ImGui::ColorEdit4(const char* label, float col[4], ImGuiColorEditFlags flag
ImGuiColorEditFlags picker_flags_to_forward = ImGuiColorEditFlags__DataTypeMask | ImGuiColorEditFlags__PickerMask | ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_AlphaBar;
ImGuiColorEditFlags picker_flags = (flags_untouched & picker_flags_to_forward) | ImGuiColorEditFlags__DisplayMask | ImGuiColorEditFlags_NoLabel | ImGuiColorEditFlags_AlphaPreviewHalf;
SetNextItemWidth(square_sz * 12.0f); // Use 256 + bar sizes?
value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x);
value_changed |= ColorPicker4("##picker", col, picker_flags, &g.ColorPickerRef.x, current_label, original_label);
EndPopup();
}
}
@ -4952,7 +4952,7 @@ static void RenderArrowsForVerticalBar(ImDrawList* draw_list, ImVec2 pos, ImVec2
// (In C++ the 'float col[4]' notation for a function argument is equivalent to 'float* col', we only specify a size to facilitate understanding of the code.)
// FIXME: we adjust the big color square height based on item width, which may cause a flickering feedback loop (if automatic height makes a vertical scrollbar appears, affecting automatic width..)
// FIXME: this is trying to be aware of style.Alpha but not fully correct. Also, the color wheel will have overlapping glitches with (style.Alpha < 1.0)
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col)
bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags flags, const float* ref_col, const char* current_label/*=NULL*/, const char* original_label/*=NULL*/)
{
ImGuiContext& g = *GImGui;
ImGuiWindow* window = GetCurrentWindow();
@ -5126,13 +5126,13 @@ bool ImGui::ColorPicker4(const char* label, float col[4], ImGuiColorEditFlags fl
PushItemFlag(ImGuiItemFlags_NoNavDefaultFocus, true);
ImVec4 col_v4(col[0], col[1], col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : col[3]);
if ((flags & ImGuiColorEditFlags_NoLabel))
Text("Current");
Text(current_label ? current_label : "Current");
ImGuiColorEditFlags sub_flags_to_forward = ImGuiColorEditFlags__InputMask | ImGuiColorEditFlags_HDR | ImGuiColorEditFlags_AlphaPreview | ImGuiColorEditFlags_AlphaPreviewHalf | ImGuiColorEditFlags_NoTooltip;
ColorButton("##current", col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2));
if (ref_col != NULL)
{
Text("Original");
Text(original_label ? original_label : "Original");
ImVec4 ref_col_v4(ref_col[0], ref_col[1], ref_col[2], (flags & ImGuiColorEditFlags_NoAlpha) ? 1.0f : ref_col[3]);
if (ColorButton("##original", ref_col_v4, (flags & sub_flags_to_forward), ImVec2(square_sz * 3, square_sz * 2)))
{

View File

@ -168,6 +168,11 @@ ToolOrdering::ToolOrdering(const Print &print, unsigned int first_extruder, bool
this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
if (this->insert_wipe_tower_extruder()) {
this->reorder_extruders(first_extruder);
this->fill_wipe_tower_partitions(print.config(), object_bottom_z, max_layer_height);
}
this->collect_extruder_statistics(prime_multi_material);
this->mark_skirt_layers(print.config(), max_layer_height);
@ -462,6 +467,27 @@ void ToolOrdering::fill_wipe_tower_partitions(const PrintConfig &config, coordf_
}
}
bool ToolOrdering::insert_wipe_tower_extruder()
{
// In case that wipe_tower_extruder is set to non-zero, we must make sure that the extruder will be in the list.
bool changed = false;
if (m_print_config_ptr->wipe_tower_extruder != 0) {
for (LayerTools& lt : m_layer_tools) {
if (lt.wipe_tower_partitions > 0) {
lt.extruders.emplace_back(m_print_config_ptr->wipe_tower_extruder - 1);
sort_remove_duplicates(lt.extruders);
changed = true;
}
}
// Now convert the 0-based list to 1-based again.
for (LayerTools& lt : m_layer_tools) {
for (auto& extruder : lt.extruders)
++extruder;
}
}
return changed;
}
void ToolOrdering::collect_extruder_statistics(bool prime_multi_material)
{
m_first_printing_extruder = (unsigned int)-1;

View File

@ -167,6 +167,7 @@ private:
void collect_extruders(const PrintObject &object, const std::vector<std::pair<double, unsigned int>> &per_layer_extruder_switches);
void reorder_extruders(unsigned int last_extruder_id);
void fill_wipe_tower_partitions(const PrintConfig &config, coordf_t object_bottom_z, coordf_t max_layer_height);
bool insert_wipe_tower_extruder();
void mark_skirt_layers(const PrintConfig &config, coordf_t max_layer_height);
void collect_extruder_statistics(bool prime_multi_material);

View File

@ -581,7 +581,7 @@ void WipeTower::set_extruder(size_t idx, const PrintConfig& config)
m_filpar.push_back(FilamentParameters());
m_filpar[idx].material = config.filament_type.get_at(idx);
m_filpar[idx].is_soluble = config.filament_soluble.get_at(idx);
m_filpar[idx].is_soluble = config.wipe_tower_extruder == 0 ? config.filament_soluble.get_at(idx) : (idx != config.wipe_tower_extruder - 1);
m_filpar[idx].temperature = config.temperature.get_at(idx);
m_filpar[idx].first_layer_temperature = config.first_layer_temperature.get_at(idx);

View File

@ -121,7 +121,7 @@ Model Model::read_from_file(const std::string& input_file, DynamicPrintConfig* c
result = load_step(input_file.c_str(), &model);
else if (boost::algorithm::iends_with(input_file, ".amf") || boost::algorithm::iends_with(input_file, ".amf.xml"))
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".3mf"))
else if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"))
//FIXME options & LoadAttribute::CheckVersion ?
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, false);
else
@ -155,7 +155,7 @@ Model Model::read_from_archive(const std::string& input_file, DynamicPrintConfig
Model model;
bool result = false;
if (boost::algorithm::iends_with(input_file, ".3mf"))
if (boost::algorithm::iends_with(input_file, ".3mf") || boost::algorithm::iends_with(input_file, ".zip"))
result = load_3mf(input_file.c_str(), *config, *config_substitutions, &model, options & LoadAttribute::CheckVersion);
else if (boost::algorithm::iends_with(input_file, ".zip.amf"))
result = load_amf(input_file.c_str(), config, config_substitutions, &model, options & LoadAttribute::CheckVersion);

View File

@ -456,7 +456,7 @@ static std::vector<std::string> s_Preset_print_options {
"top_infill_extrusion_width", "support_material_extrusion_width", "infill_overlap", "infill_anchor", "infill_anchor_max", "bridge_flow_ratio",
"elefant_foot_compensation", "xy_size_compensation", "threads", "resolution", "gcode_resolution", "wipe_tower", "wipe_tower_x", "wipe_tower_y",
"wipe_tower_width", "wipe_tower_cone_angle", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_bridging", "single_extruder_multi_material_priming", "mmu_segmented_region_max_width",
"wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"wipe_tower_extruder", "wipe_tower_no_sparse_layers", "wipe_tower_extra_spacing", "compatible_printers", "compatible_printers_condition", "inherits",
"perimeter_generator", "wall_transition_length", "wall_transition_filter_deviation", "wall_transition_angle",
"wall_distribution_count", "min_feature_size", "min_bead_width"
};

View File

@ -208,6 +208,7 @@ bool Print::invalidate_state_by_config_options(const ConfigOptionResolver & /* n
|| opt_key == "wipe_tower_bridging"
|| opt_key == "wipe_tower_extra_spacing"
|| opt_key == "wipe_tower_no_sparse_layers"
|| opt_key == "wipe_tower_extruder"
|| opt_key == "wiping_volumes_matrix"
|| opt_key == "parking_pos_retraction"
|| opt_key == "cooling_tube_retraction"
@ -328,6 +329,15 @@ std::vector<unsigned int> Print::extruders() const
std::vector<unsigned int> extruders = this->object_extruders();
append(extruders, this->support_material_extruders());
sort_remove_duplicates(extruders);
// The wipe tower extruder can also be set. When the wipe tower is enabled and it will be generated,
// append its extruder into the list too.
if (has_wipe_tower() && config().wipe_tower_extruder != 0 && extruders.size() > 1) {
assert(config().wipe_tower_extruder > 0 && config().wipe_tower_extruder < int(config().nozzle_diameter.size()));
extruders.emplace_back(config().wipe_tower_extruder - 1); // the config value is 1-based
sort_remove_duplicates(extruders);
}
return extruders;
}
@ -458,10 +468,21 @@ static inline bool sequential_print_vertical_clearance_valid(const Print &print)
boost::regex regex_g92e0 { "^[ \\t]*[gG]92[ \\t]*[eE](0(\\.0*)?|\\.0+)[ \\t]*(;.*)?$" };
// Precondition: Print::validate() requires the Print::apply() to be called its invocation.
std::string Print::validate(std::string* warning) const
std::string Print::validate(std::vector<std::string>* warnings) const
{
std::vector<unsigned int> extruders = this->extruders();
if (warnings) {
for (size_t a=0; a<extruders.size(); ++a)
for (size_t b=a+1; b<extruders.size(); ++b)
if (std::abs(m_config.bed_temperature.get_at(extruders[a]) - m_config.bed_temperature.get_at(extruders[b])) > 15
|| std::abs(m_config.first_layer_bed_temperature.get_at(extruders[a]) - m_config.first_layer_bed_temperature.get_at(extruders[b])) > 15) {
warnings->emplace_back("_BED_TEMPS_DIFFER");
goto DONE;
}
DONE:;
}
if (m_objects.empty())
return _u8L("All objects are outside of the print volume.");
@ -681,12 +702,12 @@ std::string Print::validate(std::string* warning) const
// Do we have custom support data that would not be used?
// Notify the user in that case.
if (! object->has_support() && warning) {
if (! object->has_support() && warnings) {
for (const ModelVolume* mv : object->model_object()->volumes) {
bool has_enforcers = mv->is_support_enforcer() ||
(mv->is_model_part() && mv->supported_facets.has_facets(*mv, EnforcerBlockerType::ENFORCER));
if (has_enforcers) {
*warning = "_SUPPORTS_OFF";
warnings->emplace_back("_SUPPORTS_OFF");
break;
}
}

View File

@ -547,7 +547,7 @@ public:
bool has_brim() const;
// Returns an empty string if valid, otherwise returns an error message.
std::string validate(std::string* warning = nullptr) const override;
std::string validate(std::vector<std::string>* warnings = nullptr) const override;
double skirt_first_layer_height() const;
Flow brim_flow() const;
Flow skirt_flow() const;

View File

@ -418,7 +418,7 @@ public:
virtual std::vector<ObjectID> print_object_ids() const = 0;
// Validate the print, return empty string if valid, return error if process() cannot (or should not) be started.
virtual std::string validate(std::string* warning = nullptr) const { return std::string(); }
virtual std::string validate(std::vector<std::string>* warnings = nullptr) const { return std::string(); }
enum ApplyStatus {
// No change after the Print::apply() call.

View File

@ -3235,6 +3235,27 @@ void PrintConfigDef::init_fff_params()
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionFloat(10.));
def = this->add("wipe_tower_extruder", coInt);
def->label = L("Wipe tower extruder");
def->category = L("Extruders");
def->tooltip = L("The extruder to use when printing perimeter of the wipe tower. "
"Set to 0 to use the one that is available (non-soluble would be preferred).");
def->min = 0;
def->mode = comAdvanced;
def->set_default_value(new ConfigOptionInt(0));
def = this->add("solid_infill_every_layers", coInt);
def->label = L("Solid infill every");
def->category = L("Infill");
def->tooltip = L("This feature allows to force a solid layer every given number of layers. "
"Zero to disable. You can set this to any value (for example 9999); "
"Slic3r will automatically choose the maximum possible number of layers "
"to combine according to nozzle diameter and layer height.");
def->sidetext = L("layers");
def->min = 0;
def->mode = comExpert;
def->set_default_value(new ConfigOptionInt(0));
def = this->add("xy_size_compensation", coFloat);
def->label = L("XY Size Compensation");
def->category = L("Advanced");
@ -4325,6 +4346,14 @@ void DynamicPrintConfig::normalize_fdm()
}
}
if (this->has("wipe_tower_extruder")) {
// If invalid, replace with 0.
int extruder = this->opt<ConfigOptionInt>("wipe_tower_extruder")->value;
int num_extruders = this->opt<ConfigOptionFloats>("nozzle_diameter")->size();
if (extruder < 0 || extruder > num_extruders)
this->option("wipe_tower_extruder")->setInt(0);
}
if (!this->has("solid_infill_extruder") && this->has("infill_extruder"))
this->option("solid_infill_extruder", true)->setInt(this->option("infill_extruder")->getInt());

View File

@ -829,6 +829,7 @@ PRINT_CONFIG_CLASS_DERIVED_DEFINE(
((ConfigOptionFloat, wipe_tower_cone_angle))
((ConfigOptionPercent, wipe_tower_extra_spacing))
((ConfigOptionFloat, wipe_tower_bridging))
((ConfigOptionInt, wipe_tower_extruder))
((ConfigOptionFloats, wiping_volumes_matrix))
((ConfigOptionFloats, wiping_volumes_extruders))
((ConfigOptionFloat, z_offset))

View File

@ -534,7 +534,7 @@ std::string SLAPrint::output_filename(const std::string &filename_base) const
return this->PrintBase::output_filename(m_print_config.output_filename_format.value, ".sl1", filename_base, &config);
}
std::string SLAPrint::validate(std::string*) const
std::string SLAPrint::validate(std::vector<std::string>*) const
{
for(SLAPrintObject * po : m_objects) {

View File

@ -494,7 +494,7 @@ public:
const SLAPrintStatistics& print_statistics() const { return m_print_statistics; }
std::string validate(std::string* warning = nullptr) const override;
std::string validate(std::vector<std::string>* warnings = nullptr) const override;
// An aggregation of SliceRecord-s from all the print objects for each
// occupied layer. Slice record levels dont have to match exactly.

View File

@ -307,43 +307,9 @@ public:
// Shorten the dhms time by removing the seconds, rounding the dhm to full minutes
// and removing spaces.
inline std::string short_time(const std::string &time)
{
// Parse the dhms time format.
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
if (time.find('d') != std::string::npos)
::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
else if (time.find('h') != std::string::npos)
::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
else if (time.find('m') != std::string::npos)
::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
else if (time.find('s') != std::string::npos)
::sscanf(time.c_str(), "%ds", &seconds);
// Round to full minutes.
if (days + hours + minutes > 0 && seconds >= 30) {
if (++minutes == 60) {
minutes = 0;
if (++hours == 24) {
hours = 0;
++days;
}
}
}
// Format the dhm time.
char buffer[64];
if (days > 0)
::sprintf(buffer, "%dd%dh%dm", days, hours, minutes);
else if (hours > 0)
::sprintf(buffer, "%dh%dm", hours, minutes);
else if (minutes > 0)
::sprintf(buffer, "%dm", minutes);
else
::sprintf(buffer, "%ds", seconds);
return buffer;
}
std::string short_time(const std::string& time, bool force_localization = false);
// localized short_time used on UI
inline std::string short_time_ui(const std::string& time) { return short_time(time, true); }
// Returns the given time is seconds in format DDd HHh MMm SSs
inline std::string get_time_dhms(float time_in_secs)

View File

@ -983,6 +983,61 @@ std::string xml_escape_double_quotes_attribute_value(std::string text)
return text;
}
std::string short_time(const std::string &time, bool force_localization /*= false*/)
{
// Parse the dhms time format.
int days = 0;
int hours = 0;
int minutes = 0;
int seconds = 0;
if (time.find('d') != std::string::npos)
::sscanf(time.c_str(), "%dd %dh %dm %ds", &days, &hours, &minutes, &seconds);
else if (time.find('h') != std::string::npos)
::sscanf(time.c_str(), "%dh %dm %ds", &hours, &minutes, &seconds);
else if (time.find('m') != std::string::npos)
::sscanf(time.c_str(), "%dm %ds", &minutes, &seconds);
else if (time.find('s') != std::string::npos)
::sscanf(time.c_str(), "%ds", &seconds);
// Round to full minutes.
if (days + hours + minutes > 0 && seconds >= 30) {
if (++minutes == 60) {
minutes = 0;
if (++hours == 24) {
hours = 0;
++days;
}
}
}
// Format the dhm time
if (force_localization) {
auto get_d = [days]() { return format(_u8L("%1%d"), days); };
auto get_h = [hours]() { return format(_u8L("%1%h"), hours); };
// TRN "m" means "minutes"
auto get_m = [minutes]() { return format(_u8L("%1%m"), minutes); };
if (days > 0)
return get_d() + get_h() + get_m();
if (hours > 0)
return get_h() + get_m();
if (minutes > 0)
return get_m();
return format(_u8L("%1%s"), seconds);
}
char buffer[64];
if (days > 0)
::sprintf(buffer, "%dd%dh%dm", days, hours, minutes);
else if (hours > 0)
::sprintf(buffer, "%dh%dm", hours, minutes);
else if (minutes > 0)
::sprintf(buffer, "%dm", minutes);
else
::sprintf(buffer, "%ds", seconds);
return buffer;
}
std::string format_memsize_MB(size_t n)
{
std::string out;

View File

@ -549,10 +549,10 @@ bool BackgroundSlicingProcess::empty() const
return m_print->empty();
}
std::string BackgroundSlicingProcess::validate(std::string* warning)
std::string BackgroundSlicingProcess::validate(std::vector<std::string>* warnings)
{
assert(m_print != nullptr);
return m_print->validate(warning);
return m_print->validate(warnings);
}
// Apply config over the print. Returns false, if the new config values caused any of the already

View File

@ -132,7 +132,7 @@ public:
bool empty() const;
// Validate the print. Returns an empty string if valid, returns an error message if invalid.
// Call validate before calling start().
std::string validate(std::string* warning = nullptr);
std::string validate(std::vector<std::string>* warnings = nullptr);
// Set the export path of the G-code.
// Once the path is set, the G-code

View File

@ -153,15 +153,22 @@ BundleMap BundleMap::load()
if (res.find(id) != res.end()) { continue; }
// Fresh index should be in archive_dir, otherwise look for it in cache
// Then if not in archive or cache - it could be 3rd party profile that user just copied to vendor folder (both ini and cache)
fs::path idx_path (archive_dir / (id + ".idx"));
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(warning) << format("Missing index %1% when loading bundle %2%.", idx_path.string(), id);
BOOST_LOG_TRIVIAL(error) << format("Missing index %1% when loading bundle %2%. Going to search for it in cache folder.", idx_path.string(), id);
idx_path = fs::path(cache_dir / (id + ".idx"));
}
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(error) << format("Missing index %1% when loading bundle %2%. Going to search for it in vendor folder. Is it a 3rd party profile?", idx_path.string(), id);
idx_path = fs::path(vendor_dir / (id + ".idx"));
}
if (!boost::filesystem::exists(idx_path)) {
BOOST_LOG_TRIVIAL(error) << format("Could not load bundle %1% due to missing index %2%.", id, idx_path.string());
continue;
}
Slic3r::GUI::Config::Index index;
try {
index.load(idx_path);

View File

@ -708,28 +708,28 @@ static wxString short_and_splitted_time(const std::string& time)
::sscanf(time.c_str(), "%ds", &seconds);
// Format the dhm time.
char buffer[64];
auto get_d = [days]() { return format(_u8L("%1%d"), days); };
auto get_h = [hours]() { return format(_u8L("%1%h"), hours); };
auto get_m = [minutes](){ return format(_u8L("%1%m"), minutes); };
auto get_s = [seconds](){ return format(_u8L("%1%s"), seconds); };
if (days > 0)
::sprintf(buffer, "%dd%dh\n%dm", days, hours, minutes);
else if (hours > 0) {
return format_wxstr("%1%%2%\n%3%", get_d(), get_h(), get_m());
if (hours > 0) {
if (hours < 10 && minutes < 10 && seconds < 10)
::sprintf(buffer, "%dh%dm%ds", hours, minutes, seconds);
else if (hours > 10 && minutes > 10 && seconds > 10)
::sprintf(buffer, "%dh\n%dm\n%ds", hours, minutes, seconds);
else if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10))
::sprintf(buffer, "%dh\n%dm%ds", hours, minutes, seconds);
else
::sprintf(buffer, "%dh%dm\n%ds", hours, minutes, seconds);
return format_wxstr("%1%%2%%3%", get_h(), get_m(), get_s());
if (hours > 10 && minutes > 10 && seconds > 10)
return format_wxstr("%1%\n%2%\n%3%", get_h(), get_m(), get_s());
if ((minutes < 10 && seconds > 10) || (minutes > 10 && seconds < 10))
return format_wxstr("%1%\n%2%%3%", get_h(), get_m(), get_s());
return format_wxstr("%1%%2%\n%3%", get_h(), get_m(), get_s());
}
else if (minutes > 0) {
if (minutes > 0) {
if (minutes > 10 && seconds > 10)
::sprintf(buffer, "%dm\n%ds", minutes, seconds);
else
::sprintf(buffer, "%dm%ds", minutes, seconds);
return format_wxstr("%1%\n%2%", get_m(), get_s());
return format_wxstr("%1%%2%", get_m(), get_s());
}
else
::sprintf(buffer, "%ds", seconds);
return wxString::FromUTF8(buffer);
return from_u8(get_s());
}
wxString Control::get_label(int tick, LabelType label_type/* = ltHeightWithLayer*/) const

View File

@ -9,6 +9,8 @@
#include "libslic3r/LocalesUtils.hpp"
#include "libslic3r/PresetBundle.hpp"
#include "slic3r/GUI/format.hpp"
#include "GUI_App.hpp"
#include "MainFrame.hpp"
#include "Plater.hpp"
@ -3271,6 +3273,11 @@ void GCodeViewer::render_legend(float& legend_height)
// draw text
ImGui::Dummy({ icon_size, icon_size });
ImGui::SameLine();
// localize "g" and "m" units
const std::string& grams = _u8L("g");
const std::string& inches = _u8L("in");
const std::string& metres = _CTX_utf8(L_CONTEXT("m", "Metre"), "Metre");
if (callback != nullptr) {
if (ImGui::MenuItem(label.c_str()))
callback();
@ -3306,11 +3313,9 @@ void GCodeViewer::render_legend(float& legend_height)
::sprintf(buf, "%.1f%%", 100.0f * percent);
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
ImGui::SameLine(offsets[2]);
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
imgui.text(buf);
imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres)));
ImGui::SameLine(offsets[3]);
::sprintf(buf, "%.2f g", used_filament_g);
imgui.text(buf);
imgui.text(format("%1$.2f %2%", used_filament_g, grams));
}
}
else {
@ -3330,13 +3335,10 @@ void GCodeViewer::render_legend(float& legend_height)
ImGui::TextUnformatted((percent > 0.0f) ? buf : "");
}
else if (used_filament_m > 0.0) {
char buf[64];
ImGui::SameLine(offsets[0]);
::sprintf(buf, imperial_units ? "%.2f in" : "%.2f m", used_filament_m);
imgui.text(buf);
imgui.text(format("%1$.2f %2%", used_filament_m, (imperial_units ? inches : metres)));
ImGui::SameLine(offsets[1]);
::sprintf(buf, "%.2f g", used_filament_g);
imgui.text(buf);
imgui.text(format("%1$.2f %2%", used_filament_g, grams));
}
}
@ -3506,7 +3508,7 @@ void GCodeViewer::render_legend(float& legend_height)
if (role < GCodeExtrusionRole::Count) {
labels.push_back(_u8L(gcode_extrusion_role_to_string(role)));
auto [time, percent] = role_time_and_percent(role);
times.push_back((time > 0.0f) ? short_time(get_time_dhms(time)) : "");
times.push_back((time > 0.0f) ? short_time_ui(get_time_dhms(time)) : "");
percents.push_back(percent);
max_time_percent = std::max(max_time_percent, percent);
auto [used_filament_m, used_filament_g] = used_filament_per_role(role);
@ -3630,7 +3632,7 @@ void GCodeViewer::render_legend(float& legend_height)
}
if (m_buffers[buffer_id(EMoveType::Travel)].visible)
append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time(get_time_dhms(time_mode.travel_time)),
append_item(EItemType::Line, Travel_Colors[0], _u8L("Travel"), true, short_time_ui(get_time_dhms(time_mode.travel_time)),
time_mode.travel_time / time_mode.time, max_time_percent, offsets, 0.0f, 0.0f);
break;
@ -3807,7 +3809,7 @@ void GCodeViewer::render_legend(float& legend_height)
ImGuiWrapper::to_ImU32(color2));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second - times.first)));
imgui.text(short_time_ui(get_time_dhms(times.second - times.first)));
};
auto append_print = [&imgui, imperial_units](const ColorRGBA& color, const std::array<float, 4>& offsets, const Times& times, std::pair<double, double> used_filament) {
@ -3823,9 +3825,9 @@ void GCodeViewer::render_legend(float& legend_height)
ImGuiWrapper::to_ImU32(color));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(times.second)));
imgui.text(short_time_ui(get_time_dhms(times.second)));
ImGui::SameLine(offsets[1]);
imgui.text(short_time(get_time_dhms(times.first)));
imgui.text(short_time_ui(get_time_dhms(times.first)));
if (used_filament.first > 0.0f) {
char buffer[64];
ImGui::SameLine(offsets[2]);
@ -3850,7 +3852,7 @@ void GCodeViewer::render_legend(float& legend_height)
case PartialTime::EType::Pause: { labels.push_back(_u8L("Pause")); break; }
case PartialTime::EType::ColorChange: { labels.push_back(_u8L("Color change")); break; }
}
times.push_back(short_time(get_time_dhms(item.times.second)));
times.push_back(short_time_ui(get_time_dhms(item.times.second)));
}
@ -3883,7 +3885,7 @@ void GCodeViewer::render_legend(float& legend_height)
case PartialTime::EType::Pause: {
imgui.text(_u8L("Pause"));
ImGui::SameLine(offsets[0]);
imgui.text(short_time(get_time_dhms(item.times.second - item.times.first)));
imgui.text(short_time_ui(get_time_dhms(item.times.second - item.times.first)));
break;
}
case PartialTime::EType::ColorChange: {
@ -4001,11 +4003,11 @@ void GCodeViewer::render_legend(float& legend_height)
if (ImGui::BeginTable("Times", 2)) {
if (!time_mode.layers_times.empty()) {
add_strings_row_to_table(_u8L("First layer") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
short_time(get_time_dhms(time_mode.layers_times.front())), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
short_time_ui(get_time_dhms(time_mode.layers_times.front())), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
}
add_strings_row_to_table(_u8L("Total") + ":", ImGuiWrapper::COL_ORANGE_LIGHT,
short_time(get_time_dhms(time_mode.time)), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
short_time_ui(get_time_dhms(time_mode.time)), ImGuiWrapper::to_ImVec4(ColorRGBA::WHITE()));
ImGui::EndTable();
}

View File

@ -335,7 +335,11 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const ColorRGBA& select_first_color = m_modified_extruders_colors[m_first_selected_extruder_idx];
ImVec4 first_color = ImGuiWrapper::to_ImVec4(select_first_color);
const std::string first_label = into_u8(m_desc.at("first_color")) + "##color_picker";
if (ImGui::ColorEdit4(first_label.c_str(), (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
if (ImGui::ColorEdit4(first_label.c_str(), (float*)&first_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel,
// TRN Means "current color"
_u8L("Current").c_str(),
// TRN Means "original color"
_u8L("Original").c_str()))
m_modified_extruders_colors[m_first_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(first_color);
ImGui::AlignTextToFramePadding();
@ -348,7 +352,8 @@ void GLGizmoMmuSegmentation::on_render_input_window(float x, float y, float bott
const ColorRGBA& select_second_color = m_modified_extruders_colors[m_second_selected_extruder_idx];
ImVec4 second_color = ImGuiWrapper::to_ImVec4(select_second_color);
const std::string second_label = into_u8(m_desc.at("second_color")) + "##color_picker";
if (ImGui::ColorEdit4(second_label.c_str(), (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel))
if (ImGui::ColorEdit4(second_label.c_str(), (float*)&second_color, ImGuiColorEditFlags_NoAlpha | ImGuiColorEditFlags_NoInputs | ImGuiColorEditFlags_NoLabel,
_u8L("Current").c_str(), _u8L("Original").c_str()))
m_modified_extruders_colors[m_second_selected_extruder_idx] = ImGuiWrapper::from_ImVec4(second_color);
const float max_tooltip_width = ImGui::GetFontSize() * 20.0f;

View File

@ -1370,7 +1370,7 @@ void Sidebar::update_sliced_info_sizer()
}
p->sliced_info->SetTextAndShow(siCost, str_total_cost, "Cost");
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : get_time_dhms(float(ps.estimated_print_time));
wxString t_est = std::isnan(ps.estimated_print_time) ? "N/A" : from_u8(short_time_ui(get_time_dhms(float(ps.estimated_print_time))));
p->sliced_info->SetTextAndShow(siEstimatedTime, t_est, _L("Estimated printing time") + ":");
p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + boost::nowide::narrow(t_est), p->plater->is_sidebar_collapsed());
@ -1459,14 +1459,14 @@ void Sidebar::update_sliced_info_sizer()
new_label = _L("Estimated printing time") + ":";
if (ps.estimated_normal_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("normal mode"));
info_text += format_wxstr("\n%1%", short_time(ps.estimated_normal_print_time));
info_text += format_wxstr("\n%1%", short_time_ui(ps.estimated_normal_print_time));
p->plater->get_notification_manager()->set_slicing_complete_print_time(_u8L("Estimated printing time") + ": " + ps.estimated_normal_print_time, p->plater->is_sidebar_collapsed());
}
if (ps.estimated_silent_print_time != "N/A") {
new_label += format_wxstr("\n - %1%", _L("stealth mode"));
info_text += format_wxstr("\n%1%", short_time(ps.estimated_silent_print_time));
info_text += format_wxstr("\n%1%", short_time_ui(ps.estimated_silent_print_time));
}
p->sliced_info->SetTextAndShow(siEstimatedTime, info_text, new_label);
}
@ -1702,6 +1702,7 @@ struct Plater::priv
static const std::regex pattern_zip_amf;
static const std::regex pattern_any_amf;
static const std::regex pattern_prusa;
static const std::regex pattern_zip;
priv(Plater *q, MainFrame *main_frame);
~priv();
@ -1848,7 +1849,7 @@ struct Plater::priv
void suppress_snapshots() { m_prevent_snapshots++; }
void allow_snapshots() { m_prevent_snapshots--; }
void process_validation_warning(const std::string& warning) const;
void process_validation_warning(const std::vector<std::string>& warning) const;
bool background_processing_enabled() const { return this->get_config_bool("background_processing"); }
void update_print_volume_state();
@ -1998,6 +1999,7 @@ const std::regex Plater::priv::pattern_3mf(".*3mf", std::regex::icase);
const std::regex Plater::priv::pattern_zip_amf(".*[.]zip[.]amf", std::regex::icase);
const std::regex Plater::priv::pattern_any_amf(".*[.](amf|amf[.]xml|zip[.]amf)", std::regex::icase);
const std::regex Plater::priv::pattern_prusa(".*prusa", std::regex::icase);
const std::regex Plater::priv::pattern_zip(".*zip", std::regex::icase);
Plater::priv::priv(Plater *q, MainFrame *main_frame)
: q(q)
@ -2005,7 +2007,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame)
, config(Slic3r::DynamicPrintConfig::new_from_defaults_keys({
"bed_shape", "bed_custom_texture", "bed_custom_model", "complete_objects", "duplicate_distance", "extruder_clearance_radius", "skirts", "skirt_distance",
"brim_width", "brim_separation", "brim_type", "variable_layer_height", "nozzle_diameter", "single_extruder_multi_material",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing",
"wipe_tower", "wipe_tower_x", "wipe_tower_y", "wipe_tower_width", "wipe_tower_rotation_angle", "wipe_tower_brim_width", "wipe_tower_cone_angle", "wipe_tower_extra_spacing", "wipe_tower_extruder",
"extruder_colour", "filament_colour", "material_colour", "max_print_height", "printer_model", "printer_technology",
// These values are necessary to construct SlicingParameters by the Canvas3D variable layer height editor.
"layer_height", "first_layer_height", "min_layer_height", "max_layer_height",
@ -2423,7 +2425,7 @@ void Plater::check_selected_presets_visibility(PrinterTechnology loaded_printer_
std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_files, bool load_model, bool load_config, bool imperial_units/* = false*/)
{
if (input_files.empty()) { return std::vector<size_t>(); }
if (input_files.empty()) { return std::vector<size_t>(); }
auto *nozzle_dmrs = config->opt<ConfigOptionFloats>("nozzle_diameter");
@ -2484,7 +2486,7 @@ std::vector<size_t> Plater::priv::load_files(const std::vector<fs::path>& input_
progress_dlg->Fit();
}
const bool type_3mf = std::regex_match(path.string(), pattern_3mf);
const bool type_3mf = std::regex_match(path.string(), pattern_3mf) || std::regex_match(path.string(), pattern_zip);
const bool type_zip_amf = !type_3mf && std::regex_match(path.string(), pattern_zip_amf);
const bool type_any_amf = !type_3mf && std::regex_match(path.string(), pattern_any_amf);
const bool type_prusa = std::regex_match(path.string(), pattern_prusa);
@ -3196,12 +3198,12 @@ void Plater::priv::update_print_volume_state()
this->q->model().update_print_volume_state(this->bed.build_volume());
}
void Plater::priv::process_validation_warning(const std::string& warning) const
void Plater::priv::process_validation_warning(const std::vector<std::string>& warnings) const
{
if (warning.empty())
if (warnings.empty())
notification_manager->close_notification_of_type(NotificationType::ValidateWarning);
else {
std::string text = warning;
for (std::string text : warnings) {
std::string hypertext = "";
std::function<bool(wxEvtHandler*)> action_fn = [](wxEvtHandler*){ return false; };
@ -3220,6 +3222,8 @@ void Plater::priv::process_validation_warning(const std::string& warning) const
return true;
};
}
if (text == "_BED_TEMPS_DIFFER")
text = _u8L("Bed temperatures for the used filaments differ significantly.");
notification_manager->push_notification(
NotificationType::ValidateWarning,
@ -3277,8 +3281,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// The delayed error message is no more valid.
delayed_error_message.clear();
// The state of the Print changed, and it is non-zero. Let's validate it and give the user feedback on errors.
std::string warning;
std::string err = background_process.validate(&warning);
std::vector<std::string> warnings;
std::string err = background_process.validate(&warnings);
if (err.empty()) {
notification_manager->set_all_slicing_errors_gray(true);
notification_manager->close_notification_of_type(NotificationType::ValidateError);
@ -3287,7 +3291,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
// Pass a warning from validation and either show a notification,
// or hide the old one.
process_validation_warning(warning);
process_validation_warning(warnings);
if (printer_technology == ptFFF) {
GLCanvas3D* canvas = view3D->get_canvas3d();
canvas->reset_sequential_print_clearance();
@ -3326,8 +3330,8 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
canvas->request_extra_frame();
}
}
std::string warning;
std::string err = background_process.validate(&warning);
std::vector<std::string> warnings;
std::string err = background_process.validate(&warnings);
if (!err.empty())
return return_state;
}
@ -3340,7 +3344,7 @@ unsigned int Plater::priv::update_background_process(bool force_validation, bool
//actualizate warnings
if (invalidated != Print::APPLY_STATUS_UNCHANGED || background_process.empty()) {
if (background_process.empty())
process_validation_warning(std::string());
process_validation_warning(std::vector<std::string>());
actualize_slicing_warnings(*this->background_process.current_print());
actualize_object_warnings(*this->background_process.current_print());
show_warning_dialog = false;
@ -4035,11 +4039,11 @@ void Plater::priv::on_select_preset(wxCommandEvent &evt)
// revert previously selection
const std::string& old_name = wxGetApp().preset_bundle->filaments.get_edited_preset().name;
wxGetApp().preset_bundle->set_filament_preset(idx, old_name);
combo->update();
}
else
// Synchronize config.ini with the current selections.
wxGetApp().preset_bundle->export_selections(*wxGetApp().app_config);
combo->update();
}
else if (select_preset) {
wxWindowUpdateLocker noUpdates(sidebar->presets_panel());
@ -5935,7 +5939,13 @@ bool Plater::load_files(const wxArrayString& filenames, bool delete_after_load/*
// searches for project files
for (std::vector<fs::path>::const_reverse_iterator it = paths.rbegin(); it != paths.rend(); ++it) {
std::string filename = (*it).filename().string();
if (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf")) {
bool handle_as_project = (boost::algorithm::iends_with(filename, ".3mf") || boost::algorithm::iends_with(filename, ".amf"));
if (boost::algorithm::iends_with(filename, ".zip") && is_project_3mf(it->string())) {
BOOST_LOG_TRIVIAL(warning) << "File with .zip extension is 3mf project, opening as it would have .3mf extension: " << *it;
handle_as_project = true;
}
if (handle_as_project) {
ProjectDropDialog::LoadType load_type = ProjectDropDialog::LoadType::Unknown;
{
if ((boost::algorithm::iends_with(filename, ".3mf") && !is_project_3mf(it->string())) ||

View File

@ -820,6 +820,8 @@ void PlaterPresetComboBox::update()
// Extruder color is not defined.
extruder_color.clear();
selected_filament_preset = extruder_filaments.get_selected_preset();
if (selected_filament_preset->is_dirty)
selected_filament_preset = &m_preset_bundle->filaments.get_edited_preset();
assert(selected_filament_preset);
}

View File

@ -1598,6 +1598,7 @@ void TabPrint::build()
optgroup->append_single_option_line("solid_infill_extruder");
optgroup->append_single_option_line("support_material_extruder");
optgroup->append_single_option_line("support_material_interface_extruder");
optgroup->append_single_option_line("wipe_tower_extruder");
optgroup = page->new_optgroup(L("Ooze prevention"));
optgroup->append_single_option_line("ooze_prevention");

View File

@ -32,6 +32,37 @@ namespace pt = boost::property_tree;
namespace Slic3r {
namespace {
std::string get_host_from_url(const std::string& url_in)
{
std::string url = url_in;
// add http:// if there is no scheme
size_t double_slash = url.find("//");
if (double_slash == std::string::npos)
url = "http://" + url;
std::string out = url;
CURLU* hurl = curl_url();
if (hurl) {
// Parse the input URL.
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
if (rc == CURLUE_OK) {
// Replace the address.
char* host;
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
if (rc == CURLUE_OK) {
out = host;
curl_free(host);
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url;
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url;
curl_url_cleanup(hurl);
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url";
return out;
}
#ifdef WIN32
// Workaround for Windows 10/11 mDNS resolve issue, where two mDNS resolves in succession fail.
std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
@ -96,38 +127,6 @@ std::string substitute_host(const std::string& orig_addr, std::string sub_addr)
return out;
#endif
}
std::string get_host_from_url(const std::string& url_in)
{
std::string url = url_in;
// add http:// if there is no scheme
size_t double_slash = url.find("//");
if (double_slash == std::string::npos)
url = "http://" + url;
std::string out = url;
CURLU* hurl = curl_url();
if (hurl) {
// Parse the input URL.
CURLUcode rc = curl_url_set(hurl, CURLUPART_URL, url.c_str(), 0);
if (rc == CURLUE_OK) {
// Replace the address.
char* host;
rc = curl_url_get(hurl, CURLUPART_HOST, &host, 0);
if (rc == CURLUE_OK) {
out = host;
curl_free(host);
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to get host form URL " << url;
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to parse URL " << url;
curl_url_cleanup(hurl);
}
else
BOOST_LOG_TRIVIAL(error) << "OctoPrint get_host_from_url: failed to allocate curl_url";
return out;
}
#endif // WIN32
std::string escape_string(const std::string& unescaped)
{
@ -170,7 +169,7 @@ const char* OctoPrint::get_name() const { return "OctoPrint"; }
bool OctoPrint::test_with_resolved_ip(wxString &msg) const
{
// Since the request is performed synchronously here,
// it is ok to refer to `msg` from within the closure
// it is ok to refer to `msg` from within the closure
const char* name = get_name();
bool res = true;
// Msg contains ip string.
@ -179,7 +178,15 @@ bool OctoPrint::test_with_resolved_ip(wxString &msg) const
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
std::string host = get_host_from_url(m_host);
auto http = Http::get(url);//std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
http
.on_error([&](std::string body, std::string error, unsigned status) {
@ -228,7 +235,7 @@ bool OctoPrint::test(wxString& msg) const
auto url = make_url("api/version");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
// Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
@ -378,7 +385,14 @@ bool OctoPrint::upload_inner_with_resolved_ip(PrintHostUpload upload_data, Progr
% upload_parent_path.string()
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
std::string host = get_host_from_url(m_host);
auto http = Http::post(url);//std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
@ -456,7 +470,15 @@ bool OctoPrint::upload_inner_with_host(PrintHostUpload upload_data, ProgressFn p
% upload_parent_path.string()
% (upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false");
std::string host = get_host_from_url(m_host);
auto http = Http::post(std::move(url));
// "Host" header is necessary here. In the workaround above (two mDNS..) we have got IP address from test connection and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
http.form_add("print", upload_data.post_action == PrintHostPostUploadAction::StartPrint ? "true" : "false")
.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???
@ -779,7 +801,7 @@ bool PrusaLink::test_with_method_check(wxString& msg, bool& use_put) const
auto url = make_url("api/version");
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
// Here we do not have to add custom "Host" header - the url contains host filled by user and libCurl will set the header by itself.
auto http = Http::get(std::move(url));
set_auth(http);
http.on_error([&](std::string body, std::string error, unsigned status) {
@ -852,7 +874,15 @@ bool PrusaLink::test_with_resolved_ip_and_method_check(wxString& msg, bool& use_
BOOST_LOG_TRIVIAL(info) << boost::format("%1%: Get version at: %2%") % name % url;
std::string host = get_host_from_url(m_host);
auto http = Http::get(url);//std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// Also when allow_ip_resolve = 0, this is not needed, but it should not break anything if it stays.
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
http
.on_error([&](std::string body, std::string error, unsigned status) {
@ -1006,13 +1036,18 @@ bool PrusaLink::put_inner(PrintHostUpload upload_data, std::string url, const st
bool res = true;
// Percent escape all filenames in on path and add it to the url. This is different from POST.
url += "/" + escape_path_by_element(upload_data.upload_path);
std::string host = get_host_from_url(m_host);
Http http = Http::put(std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
// This is ugly, but works. There was an error at PrusaLink side that accepts any string at Print-After-Upload as true, thus False was also triggering print after upload.
if (upload_data.post_action == PrintHostPostUploadAction::StartPrint)
http.header("Print-After-Upload", "?1");
http.set_put_body(upload_data.source_path)
.header("Content-Type", "text/x.gcode")
.header("Overwrite", "?1")
@ -1048,7 +1083,15 @@ bool PrusaLink::post_inner(PrintHostUpload upload_data, std::string url, const s
bool res = true;
const auto upload_filename = upload_data.upload_path.filename();
const auto upload_parent_path = upload_data.upload_path.parent_path();
std::string host = get_host_from_url(m_host);
Http http = Http::post(std::move(url));
// "Host" header is necessary here. We have resolved IP address and subsituted it into "url" variable.
// And when creating Http object above, libcurl automatically includes "Host" header from address it got.
// Thus "Host" is set to the resolved IP instead of host filled by user. We need to change it back.
// Not changing the host would work on the most cases (where there is 1 service on 1 hostname) but would break when f.e. reverse proxy is used (issue #9734).
// https://www.rfc-editor.org/rfc/rfc7230#section-5.4
http.header("Host", host);
set_auth(http);
set_http_post_header_args(http, upload_data.post_action);
http.form_add("path", upload_parent_path.string()) // XXX: slashes on windows ???

View File

@ -3,7 +3,7 @@
set(SLIC3R_APP_NAME "PrusaSlicer")
set(SLIC3R_APP_KEY "PrusaSlicer")
set(SLIC3R_VERSION "2.6.0-beta2")
set(SLIC3R_VERSION "2.6.0-beta3")
set(SLIC3R_BUILD_ID "PrusaSlicer-${SLIC3R_VERSION}+UNKNOWN")
set(SLIC3R_RC_VERSION "2,6,0,0")
set(SLIC3R_RC_VERSION_DOTS "2.6.0.0")