From f3b590911db3f600db288c113c90aa5e966a575f Mon Sep 17 00:00:00 2001 From: Caroline Date: Tue, 22 May 2018 18:25:02 -0500 Subject: [PATCH] Partial implementation #1405: minimum shell thickness option (#3885) * Added Minimum shell thickness option -Added the option to libslic3r -Created some of the necessary checks for illegal values * Enforce min_shell_thickness as 0 when spiral vase is enabled -This could be switched to match the extrusion width, but we already know there will only be 1 perimeter so this is probably more logical * Feature is implemented -Added a method in PerimeterGenerator to determine the amount of perimeters needed to meet min_shell_thickness * Added a test in perimeters.t * Fixed styling as requested -Removed the magic number in PerimeterGenerator::num_loops() -Added more test cases in perimeters.t -Added documentation of the new feature in slic3r.pl and README.md * Implemented requested changes -Removed the num_loops function from PerimeterGenerator -Added a scalled min_shell_thickness variable to PerimeterGenerator -Changed the loop_number logic to use a previously defined variable loops * Resolved more implementation issues -Added min shell to invalidate state method -Created additional checks in PresetEditor and LayerRegion for min shell -Fixed the tooltip to be more descriptive --- README.md | 1 + lib/Slic3r/Config.pm | 8 +++- lib/Slic3r/GUI/PresetEditor.pm | 15 +++++--- slic3r.pl | 1 + t/perimeters.t | 50 ++++++++++++++++++++++++- xs/src/libslic3r/LayerRegion.cpp | 26 +++++++------ xs/src/libslic3r/PerimeterGenerator.cpp | 18 ++++++++- xs/src/libslic3r/PrintConfig.cpp | 30 ++++++++++----- xs/src/libslic3r/PrintConfig.hpp | 2 + xs/src/libslic3r/PrintRegion.cpp | 2 + 10 files changed, 122 insertions(+), 31 deletions(-) diff --git a/README.md b/README.md index d0404855a..ec7b6d790 100644 --- a/README.md +++ b/README.md @@ -216,6 +216,7 @@ Contributions by Henrik Brix Andersen, Vojtech Bubnik, Nicolas Dandrimont, Mark --perimeters Number of perimeters/horizontal skins (range: 0+, default: 3) --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: 3) --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: 3) + --min-shell-thickness Minimum thickness of all solid shells (range: 0+, default: 0) --solid-layers Shortcut for setting the two options above at once --fill-density Infill density (range: 0%-100%, default: 40%) --fill-angle Infill angle in degrees (range: 0-90, default: 45) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 0606b0d1f..0cda8093b 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -176,6 +176,9 @@ sub validate { die "Invalid value for --filament-diameter\n" if grep $_ < 1, @{$self->filament_diameter}; + die "Invalid value for --min-shell-thickness\n" + if $self->min_shell_thickness < 0; + # --nozzle-diameter die "Invalid value for --nozzle-diameter\n" if grep $_ < 0, @{$self->nozzle_diameter}; @@ -253,7 +256,10 @@ sub validate { die "Can't make less than one perimeter when spiral vase mode is enabled\n" if $self->perimeters < 1; - + + die "Minimum shell thickness should be 0 when spiral vase mode is enabled\n" + if $self->min_shell_thickness > 0; + die "Spiral vase mode can only print hollow objects, so you need to set Fill density to 0\n" if $self->fill_density > 0; diff --git a/lib/Slic3r/GUI/PresetEditor.pm b/lib/Slic3r/GUI/PresetEditor.pm index 01c5688ca..5a5f46dc2 100644 --- a/lib/Slic3r/GUI/PresetEditor.pm +++ b/lib/Slic3r/GUI/PresetEditor.pm @@ -438,7 +438,7 @@ sub options { layer_height first_layer_height adaptive_slicing adaptive_slicing_quality match_horizontal_surfaces perimeters spiral_vase - top_solid_layers bottom_solid_layers + top_solid_layers min_shell_thickness bottom_solid_layers extra_perimeters avoid_crossing_perimeters thin_walls overhangs seam_position external_perimeters_first fill_density fill_pattern top_infill_pattern bottom_infill_pattern fill_gaps @@ -521,6 +521,7 @@ sub build { { my $optgroup = $page->new_optgroup('Vertical shells'); $optgroup->append_single_option_line('perimeters'); + $optgroup->append_single_option_line('min_shell_thickness'); $optgroup->append_single_option_line('spiral_vase'); } { @@ -800,8 +801,9 @@ sub _update { my $opt_key = $key; $opt_key = "all_keys" if (length($key // '') == 0); my $config = $self->{config}; - if (any { /$opt_key/ } qw(all_keys spiral_vase perimeters top_solid_layers fill_density support_material)) { - if ($config->spiral_vase && !($config->perimeters == 1 && $config->top_solid_layers == 0 && $config->fill_density == 0 && $config->support_material == 0)) { + + if (any { /$opt_key/ } qw(all_keys spiral_vase perimeters top_solid_layers fill_density support_material min_shell_thickness)) { + if ($config->spiral_vase && !($config->perimeters == 1 && $config->min_shell_thickness == 0 & $config->top_solid_layers == 0 && $config->fill_density == 0 && $config->support_material == 0)) { my $dialog = Wx::MessageDialog->new($self, "The Spiral Vase mode requires:\n" . "- one perimeter\n" @@ -813,6 +815,7 @@ sub _update { if ($dialog->ShowModal() == wxID_YES) { my $new_conf = Slic3r::Config->new; $new_conf->set("perimeters", 1); + $new_conf->set("min_shell_thickness", 0); $new_conf->set("top_solid_layers", 0); $new_conf->set("fill_density", 0); $new_conf->set("support_material", 0); @@ -874,8 +877,7 @@ sub _update { } } - - my $have_perimeters = $config->perimeters > 0; + my $have_perimeters = ($config->perimeters > 0) || ($config->min_shell_thickness > 0); if (any { /$opt_key/ } qw(all_keys perimeters)) { $self->get_field($_)->toggle($have_perimeters) for qw(extra_perimeters thin_walls overhangs seam_position external_perimeters_first @@ -917,7 +919,8 @@ sub _update { $self->get_field('gap_fill_speed')->toggle($have_perimeters && $have_infill && $config->fill_gaps); } - my $have_top_solid_infill = $config->top_solid_layers > 0; + my $have_top_solid_infill = ($config->top_solid_layers > 0) || ($config->min_shell_thickness > 0); + $self->get_field($_)->toggle($have_top_solid_infill) for qw(top_infill_extrusion_width top_solid_infill_speed); diff --git a/slic3r.pl b/slic3r.pl index cbd27200b..0aeba3cf7 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -439,6 +439,7 @@ $j --perimeters Number of perimeters/horizontal skins (range: 0+, default: $config->{perimeters}) --top-solid-layers Number of solid layers to do for top surfaces (range: 0+, default: $config->{top_solid_layers}) --bottom-solid-layers Number of solid layers to do for bottom surfaces (range: 0+, default: $config->{bottom_solid_layers}) + --min-shell-thickness Minimum thickness of all solid shells (range: 0+, default: 0) --solid-layers Shortcut for setting the two options above at once --fill-density Infill density (range: 0%-100%, default: $config->{fill_density}%) --fill-angle Infill angle in degrees (range: 0-90, default: $config->{fill_angle}) diff --git a/t/perimeters.t b/t/perimeters.t index 7b6618389..31fe24d0c 100644 --- a/t/perimeters.t +++ b/t/perimeters.t @@ -1,4 +1,4 @@ -use Test::More tests => 63; +use Test::More tests => 66; use strict; use warnings; @@ -313,6 +313,54 @@ use Slic3r::Test; ok !(grep { $_ % $config->perimeters } values %perimeters), 'no superfluous extra perimeters'; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('skirts', 0); + $config->set('perimeters', 3); + $config->set('min_shell_thickness', 3); + $config->set('layer_height', 0.4); + $config->set('first_layer_height', 0.35); + $config->set('extra_perimeters', 0); + $config->set('first_layer_extrusion_width', 0.5); + $config->set('perimeter_extrusion_width', 0.5); + $config->set('external_perimeter_extrusion_width', 0.5); + $config->set('cooling', 0); # to prevent speeds from being altered + $config->set('first_layer_speed', '100%'); # to prevent speeds from being altered + $config->set('perimeter_speed', 99); + $config->set('external_perimeter_speed', 99); + $config->set('small_perimeter_speed', 99); + $config->set('thin_walls', 0); + + my $test = sub { + my $print = Slic3r::Test::init_print('ipadstand', config => $config); + my %perimeters = (); # z => number of loops + my $in_loop = 0; + Slic3r::GCode::Reader->new->parse(Slic3r::Test::gcode($print), sub { + my ($self, $cmd, $args, $info) = @_; + + if ($info->{extruding} && $info->{dist_XY} > 0 && ($args->{F} // $self->F) == $config->perimeter_speed*60) { + $perimeters{$self->Z}++ if !$in_loop; + $in_loop = 1; + } else { + $in_loop = 0; + } + }); + ok !(grep { $_ % $config->min_shell_thickness/$config->perimeter_extrusion_width } values %perimeters), 'should be 6 perimeters'; + }; + + $test->(); + + $config->set('first_layer_extrusion_width', 0.54); + $config->set('perimeter_extrusion_width', 0.54); + $config->set('external_perimeter_extrusion_width', 0.54); + $test->(); + + $config->set('first_layer_extrusion_width', 0.59); + $config->set('perimeter_extrusion_width', 0.51); + $config->set('external_perimeter_extrusion_width', 0.51); + $test->(); +} + { my $config = Slic3r::Config->new_from_defaults; $config->set('nozzle_diameter', [0.4]); diff --git a/xs/src/libslic3r/LayerRegion.cpp b/xs/src/libslic3r/LayerRegion.cpp index e2c8ce576..88fa6fb1d 100644 --- a/xs/src/libslic3r/LayerRegion.cpp +++ b/xs/src/libslic3r/LayerRegion.cpp @@ -237,21 +237,23 @@ LayerRegion::prepare_fill_surfaces() the only meaningful information returned by psPerimeters. */ // if no solid layers are requested, turn top/bottom surfaces to internal - if (this->region()->config.top_solid_layers == 0) { - for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { - if (surface->surface_type == stTop) { - if (this->layer()->object()->config.infill_only_where_needed) { - surface->surface_type = stInternalVoid; - } else { - surface->surface_type = stInternal; + if (this->region()->config.min_shell_thickness == 0) { + if (this->region()->config.top_solid_layers == 0) { + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->surface_type == stTop) { + if (this->layer()->object()->config.infill_only_where_needed) { + surface->surface_type = stInternalVoid; + } else { + surface->surface_type = stInternal; + } } } } - } - if (this->region()->config.bottom_solid_layers == 0) { - for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { - if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) - surface->surface_type = stInternal; + if (this->region()->config.bottom_solid_layers == 0) { + for (Surfaces::iterator surface = this->fill_surfaces.surfaces.begin(); surface != this->fill_surfaces.surfaces.end(); ++surface) { + if (surface->surface_type == stBottom || surface->surface_type == stBottomBridge) + surface->surface_type = stInternal; + } } } diff --git a/xs/src/libslic3r/PerimeterGenerator.cpp b/xs/src/libslic3r/PerimeterGenerator.cpp index 3d1b80cc1..4fe2c27bc 100644 --- a/xs/src/libslic3r/PerimeterGenerator.cpp +++ b/xs/src/libslic3r/PerimeterGenerator.cpp @@ -38,6 +38,9 @@ PerimeterGenerator::process() // internal flow which is unrelated. coord_t min_spacing = pspacing * (1 - INSET_OVERLAP_TOLERANCE); coord_t ext_min_spacing = ext_pspacing * (1 - INSET_OVERLAP_TOLERANCE); + + // minimum shell thickness + coord_t min_shell_thickness = scale_(this->config->min_shell_thickness); // prepare grown lower layer slices for overhang detection if (this->lower_slices != NULL && this->config->overhangs) { @@ -54,8 +57,21 @@ PerimeterGenerator::process() for (Surfaces::const_iterator surface = this->slices->surfaces.begin(); surface != this->slices->surfaces.end(); ++surface) { // detect how many perimeters must be generated for this island - const int loop_number = this->config->perimeters + surface->extra_perimeters -1; // 0-indexed loops + int loops = this->config->perimeters + surface->extra_perimeters; + + // If the user has defined a minimum shell thickness compute the number of loops needed to satisfy + if (min_shell_thickness > 0) { + int min_loops = 1; + + min_loops += ceil(((float)min_shell_thickness-ext_pwidth)/pwidth); + + if (loops < min_loops) + loops = min_loops; + } + + const int loop_number = loops-1; // 0-indexed loops + Polygons gaps; Polygons last = surface->expolygon.simplify_p(SCALED_RESOLUTION); diff --git a/xs/src/libslic3r/PrintConfig.cpp b/xs/src/libslic3r/PrintConfig.cpp index b0bcc0121..533456de1 100644 --- a/xs/src/libslic3r/PrintConfig.cpp +++ b/xs/src/libslic3r/PrintConfig.cpp @@ -827,17 +827,27 @@ PrintConfigDef::PrintConfigDef() def->max = 100; def->default_value = new ConfigOptionInt(35); + + def = this->add("min_shell_thickness", coFloat); + def->label = "Minimum shell thickness"; + def->category = "Layers and Perimeters"; + def->sidetext = "mm"; + def->tooltip = "Alternative method of configuring perimeters and top/bottom layers. If this is above 0 extra perimeters and solid layers will be generated when necessary"; + def->cli = "min-shell-thickness=f"; + def->min = 0; + def->default_value = new ConfigOptionFloat(0); + def = this->add("min_layer_height", coFloats); - def->label = "Min"; - def->tooltip = "This is the lowest printable layer height for this extruder and limits the resolution for adaptive slicing. Typical values are 0.1 or 0.05."; - def->sidetext = "mm"; - def->cli = "min-layer-height=f@"; - def->min = 0; - { - ConfigOptionFloats* opt = new ConfigOptionFloats(); - opt->values.push_back(0.15); - def->default_value = opt; - } + def->label = "Min"; + def->tooltip = "This is the lowest printable layer height for this extruder and limits the resolution for adaptive slicing. Typical values are 0.1 or 0.05."; + def->sidetext = "mm"; + def->cli = "min-layer-height=f@"; + def->min = 0; + { + ConfigOptionFloats* opt = new ConfigOptionFloats(); + opt->values.push_back(0.15); + def->default_value = opt; + } def = this->add("min_print_speed", coFloat); def->label = "Min print speed"; diff --git a/xs/src/libslic3r/PrintConfig.hpp b/xs/src/libslic3r/PrintConfig.hpp index 166fb6740..5d9d096e9 100644 --- a/xs/src/libslic3r/PrintConfig.hpp +++ b/xs/src/libslic3r/PrintConfig.hpp @@ -255,6 +255,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig ConfigOptionInt infill_every_layers; ConfigOptionFloatOrPercent infill_overlap; ConfigOptionFloat infill_speed; + ConfigOptionFloat min_shell_thickness; ConfigOptionBool overhangs; ConfigOptionInt perimeter_extruder; ConfigOptionFloatOrPercent perimeter_extrusion_width; @@ -296,6 +297,7 @@ class PrintRegionConfig : public virtual StaticPrintConfig OPT_PTR(infill_every_layers); OPT_PTR(infill_overlap); OPT_PTR(infill_speed); + OPT_PTR(min_shell_thickness); OPT_PTR(overhangs); OPT_PTR(perimeter_extruder); OPT_PTR(perimeter_extrusion_width); diff --git a/xs/src/libslic3r/PrintRegion.cpp b/xs/src/libslic3r/PrintRegion.cpp index c5a1c948e..55f91f057 100644 --- a/xs/src/libslic3r/PrintRegion.cpp +++ b/xs/src/libslic3r/PrintRegion.cpp @@ -76,6 +76,7 @@ PrintRegion::invalidate_state_by_config(const PrintConfigBase &config) for (const t_config_option_key &opt_key : diff) { if (opt_key == "perimeters" || opt_key == "extra_perimeters" + || opt_key == "min_shell_thickness" || opt_key == "gap_fill_speed" || opt_key == "overhangs" || opt_key == "first_layer_extrusion_width" @@ -98,6 +99,7 @@ PrintRegion::invalidate_state_by_config(const PrintConfigBase &config) || opt_key == "solid_infill_every_layers" || opt_key == "bottom_solid_layers" || opt_key == "top_solid_layers" + || opt_key == "min_shell_thickness" || opt_key == "infill_extruder" || opt_key == "solid_infill_extruder" || opt_key == "infill_extrusion_width") {