diff --git a/src/libslic3r/CustomGCode.hpp b/src/libslic3r/CustomGCode.hpp index 5ab4c76ef..e54599ca6 100644 --- a/src/libslic3r/CustomGCode.hpp +++ b/src/libslic3r/CustomGCode.hpp @@ -42,7 +42,7 @@ enum Mode SingleExtruder, // Single extruder printer preset is selected MultiAsSingle, // Multiple extruder printer preset is selected, but // this mode works just for Single extruder print - // (For all print from objects settings is used just one extruder) + // (The same extruder is assigned to all ModelObjects and ModelVolumes). MultiExtruder // Multiple extruder printer preset is selected }; diff --git a/src/libslic3r/Layer.hpp b/src/libslic3r/Layer.hpp index b7725d11d..d66aa8f01 100644 --- a/src/libslic3r/Layer.hpp +++ b/src/libslic3r/Layer.hpp @@ -105,6 +105,7 @@ public: coordf_t slice_z; // Z used for slicing in unscaled coordinates coordf_t print_z; // Z used for printing in unscaled coordinates coordf_t height; // layer height in unscaled coordinates + coordf_t bottom_z() const { return this->print_z - this->height; } // Collection of expolygons generated by slicing the possibly multiple meshes of the source geometry // (with possibly differing extruder ID and slicing parameters) and merged. diff --git a/src/libslic3r/PrintConfig.cpp b/src/libslic3r/PrintConfig.cpp index ebb05772f..b3b686f03 100644 --- a/src/libslic3r/PrintConfig.cpp +++ b/src/libslic3r/PrintConfig.cpp @@ -168,6 +168,17 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("bottom_solid_min_thickness", coFloat); + //TRN To be shown in Print Settings "Top solid layers" + def->label = L("Bottom"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The number of bottom solid layers is increased above bottom_solid_layers if necessary to satisfy " + "minimum thickness of bottom shell."); + def->full_label = L("Minimum bottom shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("bridge_acceleration", coFloat); def->label = L("Bridge"); def->tooltip = L("This is the acceleration your printer will use for bridges. " @@ -1782,6 +1793,13 @@ void PrintConfigDef::init_fff_params() def->shortcut.push_back("bottom_solid_layers"); def->min = 0; + def = this->add("solid_min_thickness", coFloat); + def->label = L("Minimum thickness of a top / bottom shell"); + def->tooltip = L("Minimum thickness of a top / bottom shell"); + def->shortcut.push_back("top_solid_min_thickness"); + def->shortcut.push_back("bottom_solid_min_thickness"); + def->min = 0; + def = this->add("spiral_vase", coBool); def->label = L("Spiral vase"); def->tooltip = L("This feature will raise Z gradually while printing a single-walled object " @@ -2128,6 +2146,18 @@ void PrintConfigDef::init_fff_params() def->min = 0; def->set_default_value(new ConfigOptionInt(3)); + def = this->add("top_solid_min_thickness", coFloat); + //TRN To be shown in Print Settings "Top solid layers" + def->label = L("Top"); + def->category = L("Layers and Perimeters"); + def->tooltip = L("The number of top solid layers is increased above top_solid_layers if necessary to satisfy " + "minimum thickness of top shell." + " This is useful to prevent pillowing effect when printing with variable layer height."); + def->full_label = L("Minimum top shell thickness"); + def->sidetext = L("mm"); + def->min = 0; + def->set_default_value(new ConfigOptionFloat(0.)); + def = this->add("travel_speed", coFloat); def->label = L("Travel"); def->tooltip = L("Speed for travel moves (jumps between distant extrusion points)."); diff --git a/src/libslic3r/PrintConfig.hpp b/src/libslic3r/PrintConfig.hpp index 5130d3b05..c854feafc 100644 --- a/src/libslic3r/PrintConfig.hpp +++ b/src/libslic3r/PrintConfig.hpp @@ -466,6 +466,7 @@ class PrintRegionConfig : public StaticPrintConfig public: ConfigOptionFloat bridge_angle; ConfigOptionInt bottom_solid_layers; + ConfigOptionFloat bottom_solid_min_thickness; ConfigOptionFloat bridge_flow_ratio; ConfigOptionFloat bridge_speed; ConfigOptionBool ensure_vertical_shell_thickness; @@ -501,6 +502,7 @@ public: ConfigOptionBool thin_walls; ConfigOptionFloatOrPercent top_infill_extrusion_width; ConfigOptionInt top_solid_layers; + ConfigOptionFloat top_solid_min_thickness; ConfigOptionFloatOrPercent top_solid_infill_speed; ConfigOptionBool wipe_into_infill; @@ -509,6 +511,7 @@ protected: { OPT_PTR(bridge_angle); OPT_PTR(bottom_solid_layers); + OPT_PTR(bottom_solid_min_thickness); OPT_PTR(bridge_flow_ratio); OPT_PTR(bridge_speed); OPT_PTR(ensure_vertical_shell_thickness); @@ -542,6 +545,7 @@ protected: OPT_PTR(top_infill_extrusion_width); OPT_PTR(top_solid_infill_speed); OPT_PTR(top_solid_layers); + OPT_PTR(top_solid_min_thickness); OPT_PTR(wipe_into_infill); } }; diff --git a/src/libslic3r/PrintObject.cpp b/src/libslic3r/PrintObject.cpp index 5dcaf8dfb..2ff361309 100644 --- a/src/libslic3r/PrintObject.cpp +++ b/src/libslic3r/PrintObject.cpp @@ -507,7 +507,9 @@ bool PrintObject::invalidate_state_by_config_options(const std::vector<t_config_ || opt_key == "infill_every_layers" || opt_key == "solid_infill_every_layers" || opt_key == "bottom_solid_layers" + || opt_key == "bottom_solid_min_thickness" || opt_key == "top_solid_layers" + || opt_key == "top_solid_min_thickness" || opt_key == "solid_infill_below_area" || opt_key == "infill_extruder" || opt_key == "solid_infill_extruder" @@ -914,6 +916,19 @@ void PrintObject::discover_vertical_shells() Polygons bottom_surfaces; Polygons holes; }; + coordf_t min_layer_height = this->slicing_parameters().min_layer_height; + // Does this region possibly produce more than 1 top or bottom layer? + auto has_extra_layers_fn = [min_layer_height](const PrintRegionConfig &config) { + auto num_extra_layers = [min_layer_height](int num_solid_layers, coordf_t min_shell_thickness) { + if (num_solid_layers == 0) + return 0; + int n = num_solid_layers - 1; + int n2 = int(ceil(min_shell_thickness / min_layer_height)); + return std::max(n, n2 - 1); + }; + return num_extra_layers(config.top_solid_layers, config.top_solid_min_thickness) + + num_extra_layers(config.bottom_solid_layers, config.bottom_solid_min_thickness) > 0; + }; std::vector<DiscoverVerticalShellsCacheEntry> cache_top_botom_regions(m_layers.size(), DiscoverVerticalShellsCacheEntry()); bool top_bottom_surfaces_all_regions = this->region_volumes.size() > 1 && ! m_config.interface_shells.value; if (top_bottom_surfaces_all_regions) { @@ -921,11 +936,11 @@ void PrintObject::discover_vertical_shells() // is calculated over all materials. // Is the "ensure vertical wall thickness" applicable to any region? bool has_extra_layers = false; - for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++ idx_region) { - const PrintRegion ®ion = *m_print->get_region(idx_region); - if (region.config().ensure_vertical_shell_thickness.value && - (region.config().top_solid_layers.value > 1 || region.config().bottom_solid_layers.value > 1)) { + for (size_t idx_region = 0; idx_region < this->region_volumes.size(); ++idx_region) { + const PrintRegionConfig &config = m_print->get_region(idx_region)->config(); + if (config.ensure_vertical_shell_thickness.value && has_extra_layers_fn(config)) { has_extra_layers = true; + break; } } if (! has_extra_layers) @@ -1006,9 +1021,7 @@ void PrintObject::discover_vertical_shells() if (! region.config().ensure_vertical_shell_thickness.value) // This region will be handled by discover_horizontal_shells(). continue; - int n_extra_top_layers = std::max(0, region.config().top_solid_layers.value - 1); - int n_extra_bottom_layers = std::max(0, region.config().bottom_solid_layers.value - 1); - if (n_extra_top_layers + n_extra_bottom_layers == 0) + if (! has_extra_layers_fn(region.config())) // Zero or 1 layer, there is no additional vertical wall thickness enforced. continue; @@ -1049,7 +1062,7 @@ void PrintObject::discover_vertical_shells() BOOST_LOG_TRIVIAL(debug) << "Discovering vertical shells for region " << idx_region << " in parallel - start : ensure vertical wall thickness"; tbb::parallel_for( tbb::blocked_range<size_t>(0, m_layers.size(), grain_size), - [this, idx_region, n_extra_top_layers, n_extra_bottom_layers, &cache_top_botom_regions] + [this, idx_region, &cache_top_botom_regions] (const tbb::blocked_range<size_t>& range) { // printf("discover_vertical_shells from %d to %d\n", range.begin(), range.end()); for (size_t idx_layer = range.begin(); idx_layer < range.end(); ++ idx_layer) { @@ -1060,8 +1073,9 @@ void PrintObject::discover_vertical_shells() ++ debug_idx; #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - Layer *layer = m_layers[idx_layer]; - LayerRegion *layerm = layer->m_regions[idx_region]; + Layer *layer = m_layers[idx_layer]; + LayerRegion *layerm = layer->m_regions[idx_region]; + const PrintRegionConfig ®ion_config = layerm->region()->config(); #ifdef SLIC3R_DEBUG_SLICE_PROCESSING layerm->export_region_slices_to_svg_debug("4_discover_vertical_shells-initial"); @@ -1101,30 +1115,47 @@ void PrintObject::discover_vertical_shells() } } #endif /* SLIC3R_DEBUG_SLICE_PROCESSING */ - // Reset the top / bottom inflated regions caches of entries, which are out of the moving window. - bool hole_first = true; - for (int n = (int)idx_layer - n_extra_bottom_layers; n <= (int)idx_layer + n_extra_top_layers; ++ n) - if (n >= 0 && n < (int)m_layers.size()) { - const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[n]; - if (hole_first) { - hole_first = false; - polygons_append(holes, cache.holes); - } - else if (! holes.empty()) { - holes = intersection(holes, cache.holes); - } - size_t n_shell_old = shell.size(); - if (n > int(idx_layer)) - // Collect top surfaces. - polygons_append(shell, cache.top_surfaces); - else if (n < int(idx_layer)) - // Collect bottom and bottom bridge surfaces. - polygons_append(shell, cache.bottom_surfaces); - // Running the union_ using the Clipper library piece by piece is cheaper - // than running the union_ all at once. - if (n_shell_old < shell.size()) - shell = union_(shell, false); - } + polygons_append(holes, cache_top_botom_regions[idx_layer].holes); + { + // Gather top regions projected to this layer. + coordf_t print_z = layer->print_z; + int n_top_layers = region_config.top_solid_layers.value; + for (int i = int(idx_layer) + 1; + i < int(m_layers.size()) && + (i < int(idx_layer) + n_top_layers || + m_layers[i]->print_z - print_z < region_config.top_solid_min_thickness - EPSILON); + ++ i) { + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + if (! holes.empty()) + holes = intersection(holes, cache.holes); + if (! cache.top_surfaces.empty()) { + polygons_append(shell, cache.top_surfaces); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell, false); + } + } + } + { + // Gather bottom regions projected to this layer. + coordf_t bottom_z = layer->bottom_z(); + int n_bottom_layers = region_config.bottom_solid_layers.value; + for (int i = int(idx_layer) - 1; + i >= 0 && + (i > int(idx_layer) - n_bottom_layers || + bottom_z - m_layers[i]->bottom_z() < region_config.bottom_solid_min_thickness - EPSILON); + -- i) { + const DiscoverVerticalShellsCacheEntry &cache = cache_top_botom_regions[i]; + if (! holes.empty()) + holes = intersection(holes, cache.holes); + if (! cache.bottom_surfaces.empty()) { + polygons_append(shell, cache.bottom_surfaces); + // Running the union_ using the Clipper library piece by piece is cheaper + // than running the union_ all at once. + shell = union_(shell, false); + } + } + } #ifdef SLIC3R_DEBUG_SLICE_PROCESSING { Slic3r::SVG svg(debug_out_path("discover_vertical_shells-perimeters-before-union-%d.svg", debug_idx), get_extents(shell)); @@ -2280,7 +2311,8 @@ void PrintObject::discover_horizontal_shells() for (size_t region_id = 0; region_id < this->region_volumes.size(); ++ region_id) { for (size_t i = 0; i < m_layers.size(); ++ i) { m_print->throw_if_canceled(); - LayerRegion *layerm = m_layers[i]->regions()[region_id]; + Layer *layer = m_layers[i]; + LayerRegion *layerm = layer->regions()[region_id]; const PrintRegionConfig ®ion_config = layerm->region()->config(); if (region_config.solid_infill_every_layers.value > 0 && region_config.fill_density.value > 0 && (i % region_config.solid_infill_every_layers) == 0) { @@ -2295,6 +2327,8 @@ void PrintObject::discover_horizontal_shells() if (region_config.ensure_vertical_shell_thickness.value) continue; + coordf_t print_z = layer->print_z; + coordf_t bottom_z = layer->bottom_z(); for (size_t idx_surface_type = 0; idx_surface_type < 3; ++ idx_surface_type) { m_print->throw_if_canceled(); SurfaceType type = (idx_surface_type == 0) ? stTop : (idx_surface_type == 1) ? stBottom : stBottomBridge; @@ -2323,10 +2357,15 @@ void PrintObject::discover_horizontal_shells() continue; // Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == stTop) ? 'top' : 'bottom'; - size_t solid_layers = (type == stTop) ? region_config.top_solid_layers.value : region_config.bottom_solid_layers.value; - for (int n = (type == stTop) ? i-1 : i+1; std::abs(n - (int)i) < solid_layers; (type == stTop) ? -- n : ++ n) { - if (n < 0 || n >= int(m_layers.size())) - continue; + // Scatter top / bottom regions to other layers. Scattering process is inherently serial, it is difficult to parallelize without locking. + for (int n = (type == stTop) ? int(i) - 1 : int(i) + 1; + (type == stTop) ? + (n >= 0 && (int(i) - n < region_config.top_solid_layers.value || + print_z - m_layers[n]->print_z < region_config.top_solid_min_thickness.value - EPSILON)) : + (n < int(m_layers.size()) && (n - int(i) < region_config.bottom_solid_layers.value || + m_layers[n]->bottom_z() - bottom_z < region_config.bottom_solid_min_thickness.value - EPSILON)); + (type == stTop) ? -- n : ++ n) + { // Slic3r::debugf " looking for neighbors on layer %d...\n", $n; // Reference to the lower layer of a TOP surface, or an upper layer of a BOTTOM surface. LayerRegion *neighbor_layerm = m_layers[n]->regions()[region_id]; diff --git a/src/libslic3r/Slicing.cpp b/src/libslic3r/Slicing.cpp index 2a32ba5ef..82d2d1989 100644 --- a/src/libslic3r/Slicing.cpp +++ b/src/libslic3r/Slicing.cpp @@ -41,6 +41,23 @@ inline coordf_t max_layer_height_from_nozzle(const PrintConfig &print_config, in return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); } +// Minimum layer height for the variable layer height algorithm. +coordf_t Slicing::min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +{ + coordf_t min_layer_height = print_config.opt_float("min_layer_height", idx_nozzle - 1); + return (min_layer_height == 0.) ? MIN_LAYER_HEIGHT_DEFAULT : std::max(MIN_LAYER_HEIGHT, min_layer_height); +} + +// Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, +// it should not be smaller than the minimum layer height. +coordf_t Slicing::max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle) +{ + coordf_t min_layer_height = min_layer_height_from_nozzle(print_config, idx_nozzle); + coordf_t max_layer_height = print_config.opt_float("max_layer_height", idx_nozzle - 1); + coordf_t nozzle_dmr = print_config.opt_float("nozzle_diameter", idx_nozzle - 1); + return std::max(min_layer_height, (max_layer_height == 0.) ? (0.75 * nozzle_dmr) : max_layer_height); +} + SlicingParameters SlicingParameters::create_from_config( const PrintConfig &print_config, const PrintObjectConfig &object_config, diff --git a/src/libslic3r/Slicing.hpp b/src/libslic3r/Slicing.hpp index 036344b22..95cf6891b 100644 --- a/src/libslic3r/Slicing.hpp +++ b/src/libslic3r/Slicing.hpp @@ -99,7 +99,6 @@ struct SlicingParameters }; static_assert(IsTriviallyCopyable<SlicingParameters>::value, "SlicingParameters class is not POD (and it should be - see constructor)."); - // The two slicing parameters lead to the same layering as long as the variable layer thickness is not in action. inline bool equal_layering(const SlicingParameters &sp1, const SlicingParameters &sp2) { @@ -183,7 +182,17 @@ extern int generate_layer_height_texture( const std::vector<coordf_t> &layers, void *data, int rows, int cols, bool level_of_detail_2nd_level); -}; // namespace Slic3r +namespace Slicing { + // Minimum layer height for the variable layer height algorithm. Nozzle index is 1 based. + coordf_t min_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); + + // Maximum layer height for the variable layer height algorithm, 3/4 of a nozzle dimaeter by default, + // it should not be smaller than the minimum layer height. + // Nozzle index is 1 based. + coordf_t max_layer_height_from_nozzle(const DynamicPrintConfig &print_config, int idx_nozzle); +} // namespace Slicing + +} // namespace Slic3r namespace cereal { diff --git a/src/slic3r/GUI/ConfigManipulation.cpp b/src/slic3r/GUI/ConfigManipulation.cpp index 966f34761..a8773d736 100644 --- a/src/slic3r/GUI/ConfigManipulation.cpp +++ b/src/slic3r/GUI/ConfigManipulation.cpp @@ -233,22 +233,27 @@ void ConfigManipulation::toggle_print_fff_options(DynamicPrintConfig* config) "solid_infill_every_layers", "solid_infill_below_area", "infill_extruder" }) toggle_field(el, have_infill); - bool have_solid_infill = config->opt_int("top_solid_layers") > 0 || config->opt_int("bottom_solid_layers") > 0; + bool has_spiral_vase = config->opt_bool("spiral_vase"); + bool has_top_solid_infill = config->opt_int("top_solid_layers") > 0; + bool has_bottom_solid_infill = config->opt_int("bottom_solid_layers") > 0; + bool has_solid_infill = has_top_solid_infill || has_bottom_solid_infill; // solid_infill_extruder uses the same logic as in Print::extruders() for (auto el : { "top_fill_pattern", "bottom_fill_pattern", "infill_first", "solid_infill_extruder", "solid_infill_extrusion_width", "solid_infill_speed" }) - toggle_field(el, have_solid_infill); + toggle_field(el, has_solid_infill); for (auto el : { "fill_angle", "bridge_angle", "infill_extrusion_width", "infill_speed", "bridge_speed" }) - toggle_field(el, have_infill || have_solid_infill); + toggle_field(el, have_infill || has_solid_infill); + + toggle_field("top_solid_min_thickness", ! has_spiral_vase && has_top_solid_infill); + toggle_field("bottom_solid_min_thickness", ! has_spiral_vase && has_bottom_solid_infill); // Gap fill is newly allowed in between perimeter lines even for empty infill (see GH #1476). toggle_field("gap_fill_speed", have_perimeters); - bool have_top_solid_infill = config->opt_int("top_solid_layers") > 0; for (auto el : { "top_infill_extrusion_width", "top_solid_infill_speed" }) - toggle_field(el, have_top_solid_infill); + toggle_field(el, has_top_solid_infill); bool have_default_acceleration = config->opt_float("default_acceleration") > 0; for (auto el : { "perimeter_acceleration", "infill_acceleration", diff --git a/src/slic3r/GUI/Plater.cpp b/src/slic3r/GUI/Plater.cpp index 1b69decfc..aadfdd0dd 100644 --- a/src/slic3r/GUI/Plater.cpp +++ b/src/slic3r/GUI/Plater.cpp @@ -1980,7 +1980,7 @@ Plater::priv::priv(Plater *q, MainFrame *main_frame) "extruder_colour", "filament_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", - "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", "bottom_solid_layers", "solid_infill_extruder", + "brim_width", "perimeters", "perimeter_extruder", "fill_density", "infill_extruder", "top_solid_layers", "support_material", "support_material_extruder", "support_material_interface_extruder", "support_material_contact_distance", "raft_layers" })) , sidebar(new Sidebar(q)) diff --git a/src/slic3r/GUI/Preset.cpp b/src/slic3r/GUI/Preset.cpp index 98fcf3f42..00e004f75 100644 --- a/src/slic3r/GUI/Preset.cpp +++ b/src/slic3r/GUI/Preset.cpp @@ -386,7 +386,8 @@ void Preset::set_visible_from_appconfig(const AppConfig &app_config) const std::vector<std::string>& Preset::print_options() { static std::vector<std::string> s_opts { - "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", "top_solid_layers", "bottom_solid_layers", + "layer_height", "first_layer_height", "perimeters", "spiral_vase", "slice_closing_radius", + "top_solid_layers", "top_solid_min_thickness", "bottom_solid_layers", "bottom_solid_min_thickness", "extra_perimeters", "ensure_vertical_shell_thickness", "avoid_crossing_perimeters", "thin_walls", "overhangs", "seam_position", "external_perimeters_first", "fill_density", "fill_pattern", "top_fill_pattern", "bottom_fill_pattern", "infill_every_layers", "infill_only_where_needed", "solid_infill_every_layers", "fill_angle", "bridge_angle", diff --git a/src/slic3r/GUI/PresetHints.cpp b/src/slic3r/GUI/PresetHints.cpp index f6281d7af..71db6d35b 100644 --- a/src/slic3r/GUI/PresetHints.cpp +++ b/src/slic3r/GUI/PresetHints.cpp @@ -1,6 +1,7 @@ #include <cassert> #include "libslic3r/Flow.hpp" +#include "libslic3r/Slicing.hpp" #include "libslic3r/libslic3r.h" #include "PresetBundle.hpp" @@ -242,7 +243,7 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre float nozzle_diameter = float(printer_config.opt_float("nozzle_diameter", 0)); std::string out; - if (layer_height <= 0.f){ + if (layer_height <= 0.f) { out += _utf8(L("Recommended object thin wall thickness: Not available due to invalid layer height.")); return out; } @@ -272,4 +273,70 @@ std::string PresetHints::recommended_thin_wall_thickness(const PresetBundle &pre return out; } + +// Produce a textual explanation of the combined effects of the top/bottom_solid_layers +// versus top/bottom_min_shell_thickness. Which of the two values wins depends +// on the active layer height. +std::string PresetHints::top_bottom_shell_thickness_explanation(const PresetBundle &preset_bundle) +{ + const DynamicPrintConfig &print_config = preset_bundle.prints .get_edited_preset().config; + const DynamicPrintConfig &printer_config = preset_bundle.printers .get_edited_preset().config; + + std::string out; + + int top_solid_layers = print_config.opt_int("top_solid_layers"); + int bottom_solid_layers = print_config.opt_int("bottom_solid_layers"); + bool has_top_layers = top_solid_layers > 0; + bool has_bottom_layers = bottom_solid_layers > 0; + bool has_shell = has_top_layers && has_bottom_layers; + double top_solid_min_thickness = print_config.opt_float("top_solid_min_thickness"); + double bottom_solid_min_thickness = print_config.opt_float("bottom_solid_min_thickness"); + double layer_height = print_config.opt_float("layer_height"); + bool variable_layer_height = printer_config.opt_bool("variable_layer_height"); + //FIXME the following lines take into account the 1st extruder only. + double min_layer_height = (has_shell && variable_layer_height) ? Slicing::min_layer_height_from_nozzle(printer_config, 1) : layer_height; + double max_layer_height = (has_shell && variable_layer_height) ? Slicing::max_layer_height_from_nozzle(printer_config, 1) : layer_height; + + if (layer_height <= 0.f) { + out += _utf8(L("Top / bottom shell thickness hint: Not available due to invalid layer height.")); + return out; + } + + if (has_top_layers) { + double top_shell_thickness = top_solid_layers * layer_height; + if (top_shell_thickness < top_solid_min_thickness) { + // top_solid_min_shell_thickness triggers even in case of normal layer height. Round the top_shell_thickness up + // to an integer multiply of layer_height. + double n = ceil(top_solid_min_thickness / layer_height); + top_shell_thickness = n * layer_height; + } + double top_shell_thickness_minimum = std::max(top_solid_min_thickness, top_solid_layers * min_layer_height); + out += (boost::format(_utf8(L("Top shell is %1% mm thick for layer height %2% mm."))) % top_shell_thickness % layer_height).str(); + if (variable_layer_height && top_shell_thickness_minimum < top_shell_thickness) { + out += " "; + out += (boost::format(_utf8(L("Minimum top shell thickness is %1% mm."))) % top_shell_thickness_minimum).str(); + } + } + + if (has_bottom_layers) { + double bottom_shell_thickness = bottom_solid_layers * layer_height; + if (bottom_shell_thickness < bottom_solid_min_thickness) { + // bottom_solid_min_shell_thickness triggers even in case of normal layer height. Round the bottom_shell_thickness up + // to an integer multiply of layer_height. + double n = ceil(bottom_solid_min_thickness / layer_height); + bottom_shell_thickness = n * layer_height; + } + double bottom_shell_thickness_minimum = std::max(bottom_solid_min_thickness, bottom_solid_layers * min_layer_height); + if (! out.empty()) + out += "\n"; + out += (boost::format(_utf8(L("Bottom shell is %1% mm thick for layer height %2% mm."))) % bottom_shell_thickness % layer_height).str(); + if (variable_layer_height && bottom_shell_thickness_minimum < bottom_shell_thickness) { + out += " "; + out += (boost::format(_utf8(L("Minimum bottom shell thickness is %1% mm."))) % bottom_shell_thickness_minimum).str(); + } + } + + return out; +} + }; // namespace Slic3r diff --git a/src/slic3r/GUI/PresetHints.hpp b/src/slic3r/GUI/PresetHints.hpp index 39bf0b100..be049c2c8 100644 --- a/src/slic3r/GUI/PresetHints.hpp +++ b/src/slic3r/GUI/PresetHints.hpp @@ -23,6 +23,11 @@ public: // Produce a textual description of a recommended thin wall thickness // from the provided number of perimeters and the external / internal perimeter width. static std::string recommended_thin_wall_thickness(const PresetBundle &preset_bundle); + + // Produce a textual explanation of the combined effects of the top/bottom_solid_layers + // versus top/bottom_min_shell_thickness. Which of the two values wins depends + // on the active layer height. + static std::string top_bottom_shell_thickness_explanation(const PresetBundle &preset_bundle); }; } // namespace Slic3r diff --git a/src/slic3r/GUI/Tab.cpp b/src/slic3r/GUI/Tab.cpp index 72e209167..5a41f12a5 100644 --- a/src/slic3r/GUI/Tab.cpp +++ b/src/slic3r/GUI/Tab.cpp @@ -1056,6 +1056,16 @@ void TabPrint::build() line.append_option(optgroup->get_option("top_solid_layers")); line.append_option(optgroup->get_option("bottom_solid_layers")); optgroup->append_line(line); + line = { _(L("Minimum shell thickness")), "" }; + line.append_option(optgroup->get_option("top_solid_min_thickness")); + line.append_option(optgroup->get_option("bottom_solid_min_thickness")); + optgroup->append_line(line); + line = { "", "" }; + line.full_width = 1; + line.widget = [this](wxWindow* parent) { + return description_line_widget(parent, &m_top_bottom_shell_thickness_explanation); + }; + optgroup->append_line(line); optgroup = page->new_optgroup(_(L("Quality (slower slicing)"))); optgroup->append_single_option_line("extra_perimeters"); @@ -1277,6 +1287,8 @@ void TabPrint::update() m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); Layout(); // Thaw(); @@ -1295,6 +1307,8 @@ void TabPrint::OnActivate() { m_recommended_thin_wall_thickness_description_line->SetText( from_u8(PresetHints::recommended_thin_wall_thickness(*m_preset_bundle))); + m_top_bottom_shell_thickness_explanation->SetText( + from_u8(PresetHints::top_bottom_shell_thickness_explanation(*m_preset_bundle))); Tab::OnActivate(); } diff --git a/src/slic3r/GUI/Tab.hpp b/src/slic3r/GUI/Tab.hpp index cfa5ae56d..c88a74e53 100644 --- a/src/slic3r/GUI/Tab.hpp +++ b/src/slic3r/GUI/Tab.hpp @@ -327,8 +327,9 @@ public: Tab(parent, _(L("Print Settings")), Slic3r::Preset::TYPE_PRINT) {} ~TabPrint() {} - ogStaticText* m_recommended_thin_wall_thickness_description_line; - bool m_support_material_overhangs_queried = false; + ogStaticText* m_recommended_thin_wall_thickness_description_line = nullptr; + ogStaticText* m_top_bottom_shell_thickness_explanation = nullptr; + bool m_support_material_overhangs_queried = false; void build() override; void reload_config() override; @@ -336,6 +337,7 @@ public: void OnActivate() override; bool supports_printer_technology(const PrinterTechnology tech) override { return tech == ptFFF; } }; + class TabFilament : public Tab { ogStaticText* m_volumetric_speed_description_line;