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
This commit is contained in:
Caroline 2018-05-22 18:25:02 -05:00 committed by Joseph Lenox
parent 61ace41351
commit f3b590911d
10 changed files with 122 additions and 31 deletions

View File

@ -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)

View File

@ -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;

View File

@ -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);

View File

@ -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})

View File

@ -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]);

View File

@ -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;
}
}
}

View File

@ -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);

View File

@ -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";

View File

@ -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);

View File

@ -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") {