diff --git a/README.markdown b/README.markdown index ad304dc1b9..ca8e7a3466 100644 --- a/README.markdown +++ b/README.markdown @@ -172,6 +172,9 @@ The author of the Silk icon set is Mark James. --bridge-acceleration Overrides firmware's default acceleration for bridges. (mm/s^2, set zero to disable; default: 0) + --first-layer-acceleration + Overrides firmware's default acceleration for first layer. (mm/s^2, set zero + to disable; default: 0) --default-acceleration Acceleration will be reset to this value after the specific settings above have been applied. (mm/s^2, set zero to disable; default: 130) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index bad999ee76..7c54b198c9 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -79,6 +79,7 @@ use constant OVERLAP_FACTOR => 1; use constant SMALL_PERIMETER_LENGTH => (6.5 / SCALING_FACTOR) * 2 * PI; use constant LOOP_CLIPPING_LENGTH_OVER_SPACING => 0.15; use constant INFILL_OVERLAP_OVER_SPACING => 0.45; +use constant EXTERNAL_INFILL_MARGIN => 3; our $Config; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 0d89b79ee2..a48f2f0f41 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -369,6 +369,14 @@ our $Options = { type => 'f', default => 0, }, + 'first_layer_acceleration' => { + label => 'First layer', + tooltip => 'This is the acceleration your printer will use for first layer. Set zero to disable acceleration control for first layer.', + sidetext => 'mm/s²', + cli => 'first-layer-acceleration=f', + type => 'f', + default => 0, + }, # accuracy options 'layer_height' => { @@ -1377,6 +1385,11 @@ sub validate { die "Invalid value for --extrusion-multiplier\n" if defined first { $_ <= 0 } @{$self->extrusion_multiplier}; + # --default-acceleration + die "Invalid zero value for --default-acceleration when using other acceleration settings\n" + if ($self->perimeter_acceleration || $self->infill_acceleration || $self->bridge_acceleration || $self->first_layer_acceleration) + && !$self->default_acceleration; + # general validation, quick and dirty foreach my $opt_key (keys %$Options) { my $opt = $Options->{$opt_key}; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 37cd9f2f39..b76cbef792 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -127,6 +127,14 @@ sub make_fill { # add spacing between surfaces @surfaces = map @{$_->offset(-$distance_between_surfaces / 2 * &Slic3r::INFILL_OVERLAP_OVER_SPACING)}, @surfaces; + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("fill_" . $layerm->print_z . ".svg", + expolygons => [ map $_->expolygon, grep !$_->is_solid, @surfaces ], + red_expolygons => [ map $_->expolygon, grep $_->is_solid, @surfaces ], + ); + } + my @fills = (); my @fills_ordering_points = (); SURFACE: foreach my $surface (@surfaces) { diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 6108c120c1..161b8db634 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -32,9 +32,8 @@ sub fill_surface { $flow_spacing = unscale $line_spacing; } else { # extend bounding box so that our pattern will be aligned with other layers - # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one - $bounding_box->extents->[X][MIN] -= $bounding_box->x_min; - $bounding_box->extents->[Y][MIN] -= $bounding_box->y_min; + $bounding_box->extents->[X][MIN] -= $bounding_box->x_min % $line_spacing; + $bounding_box->extents->[Y][MIN] -= $bounding_box->y_min % $line_spacing; } # generate the basic pattern diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 4b0394f1e1..a06a1b1853 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -14,8 +14,9 @@ has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); has 'enable_wipe' => (is => 'lazy'); # at least one extruder has wipe enabled has 'layer_count' => (is => 'ro', required => 1 ); has 'layer' => (is => 'rw'); +has '_layer_islands' => (is => 'rw'); +has '_upper_layer_islands' => (is => 'rw'); has '_layer_overhangs' => (is => 'rw'); -has 'move_z_callback' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw'); @@ -98,7 +99,14 @@ sub change_layer { $self->layer($layer); - # avoid computing overhangs if they're not needed + # avoid computing islands and overhangs if they're not needed + if ($self->config->only_retract_when_crossing_perimeters) { + $self->_layer_islands([ $layer->islands ]); + $self->_upper_layer_islands([ $layer->upper_layer_islands ]); + } else { + $self->_layer_islands([]); + $self->_upper_layer_islands([]); + } $self->_layer_overhangs( $layer->id > 0 && ($Slic3r::Config->overhangs || $Slic3r::Config->start_perimeters_at_non_overhang) ? [ map $_->expolygon, grep $_->surface_type == S_TYPE_BOTTOM, map @{$_->slices}, @{$layer->regions} ] @@ -116,6 +124,15 @@ sub change_layer { int(99 * ($layer->id / ($self->layer_count - 1))), ($self->config->gcode_comments ? ' ; update progress' : ''); } + if ($self->config->first_layer_acceleration) { + if ($layer->id == 0) { + $gcode .= $self->set_acceleration($self->config->first_layer_acceleration); + } elsif ($layer->id == 1) { + $gcode .= $self->set_acceleration($self->config->default_acceleration); + } + } + + $gcode .= $self->move_z($layer->print_z); return $gcode; } @@ -137,7 +154,6 @@ sub move_z { $self->speed('travel'); $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) if !defined $self->z || abs($z - ($self->z - $self->lifted)) > epsilon; - $gcode .= $self->move_z_callback->() if defined $self->move_z_callback; } elsif ($z < $self->z && $z > ($self->z - $self->lifted + epsilon)) { # we're moving to a layer height which is greater than the nominal current one # (nominal = actual - lifted) and less than the actual one. we're basically @@ -286,14 +302,16 @@ sub extrude_path { # adjust acceleration my $acceleration; - if ($self->config->perimeter_acceleration && $path->is_perimeter) { - $acceleration = $self->config->perimeter_acceleration; - } elsif ($self->config->infill_acceleration && $path->is_fill) { - $acceleration = $self->config->infill_acceleration; - } elsif ($self->config->infill_acceleration && $path->is_bridge) { - $acceleration = $self->config->bridge_acceleration; + if (!$self->config->first_layer_acceleration || $self->layer->id != 0) { + if ($self->config->perimeter_acceleration && $path->is_perimeter) { + $acceleration = $self->config->perimeter_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_fill) { + $acceleration = $self->config->infill_acceleration; + } elsif ($self->config->infill_acceleration && $path->is_bridge) { + $acceleration = $self->config->bridge_acceleration; + } + $gcode .= $self->set_acceleration($acceleration) if $acceleration; } - $gcode .= $self->set_acceleration($acceleration) if $acceleration; my $area; # mm^3 of extrudate per mm of tool movement if ($path->is_bridge) { @@ -361,9 +379,9 @@ sub travel_to { # *and* in an island in the upper layer (so that the ooze will not be visible) if ($travel->length < scale $self->extruder->retract_before_travel || ($self->config->only_retract_when_crossing_perimeters - && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) - && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices})) - || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) + && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->_upper_layer_islands}) + && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->_layer_islands})) + || ($role == EXTR_ROLE_SUPPORTMATERIAL && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->support_islands})) ) { $self->straight_once(0); $self->speed('travel'); @@ -405,9 +423,9 @@ sub _plan { my $need_retract = !$self->config->only_retract_when_crossing_perimeters; if (!$need_retract) { $need_retract = 1; - foreach my $slice (@{$self->layer->upper_layer_slices}) { + foreach my $island ($self->layer->upper_layer_islands) { # discard the island if at any line is not enclosed in it - next if first { !$slice->encloses_line($_, scaled_epsilon) } @travel; + next if first { !$island->encloses_line($_, scaled_epsilon) } @travel; # okay, this island encloses the full travel path $need_retract = 0; last; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index ec8af577bd..8569ab1a2b 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -46,23 +46,16 @@ sub process_layer { $self->second_layer_things_done(1); } - # set new layer, but don't move Z as support material contact areas may need an intermediate one + # set new layer - this will change Z and force a retraction if retract_layer_change is enabled $gcode .= $self->gcodegen->change_layer($layer); - - # prepare callback to call as soon as a Z command is generated - $self->gcodegen->move_z_callback(sub { - $self->gcodegen->move_z_callback(undef); # circular ref or not? - return "" if !$Slic3r::Config->layer_gcode; - return $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode, { - layer_num => $self->gcodegen->layer->id, - }) . "\n"; - }); + $gcode .= $Slic3r::Config->replace_options($Slic3r::Config->layer_gcode, { + layer_num => $self->gcodegen->layer->id, + }) . "\n" if $Slic3r::Config->layer_gcode; # extrude skirt if ((values %{$self->skirt_done}) < $Slic3r::Config->skirt_height && !$self->skirt_done->{$layer->print_z}) { $self->gcodegen->set_shift(@{$self->shift}); - $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($layer->print_z); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[0]); # skip skirt if we have a large brim if ($layer->id < $Slic3r::Config->skirt_height) { # distribute skirt loops across all extruders @@ -81,8 +74,7 @@ sub process_layer { # extrude brim if (!$self->brim_done) { - $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); # move_z requires extruder - $gcode .= $self->gcodegen->move_z($layer->print_z); + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); $self->gcodegen->set_shift(@{$self->shift}); $gcode .= $self->gcodegen->extrude_loop($_, 'brim') for @{$self->print->brim}; $self->brim_done(1); @@ -98,7 +90,6 @@ sub process_layer { # extrude support material before other things because it might use a lower Z # and also because we avoid travelling on other things when printing it if ($self->print->has_support_material && $layer->isa('Slic3r::Layer::Support')) { - $gcode .= $self->gcodegen->move_z($layer->print_z); if ($layer->support_interface_fills) { $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_interface_extruder-1]); $gcode .= $self->gcodegen->extrude_path($_, 'support material interface') @@ -111,9 +102,6 @@ sub process_layer { } } - # set actual Z - this will force a retraction - $gcode .= $self->gcodegen->move_z($layer->print_z); - # tweak region ordering to save toolchanges my @region_ids = 0 .. ($self->print->regions_count-1); if ($self->gcodegen->multiple_extruders) { diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 70fd3962a8..bdf5d82695 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -461,7 +461,7 @@ sub build { }, { title => 'Acceleration control (advanced)', - options => [qw(perimeter_acceleration infill_acceleration bridge_acceleration default_acceleration)], + options => [qw(perimeter_acceleration infill_acceleration bridge_acceleration first_layer_acceleration default_acceleration)], }, ]); diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index bd8b394da5..f99587c0e5 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -29,7 +29,7 @@ sub union { my ($polygons, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; $clipper->clear; - $clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons); + $clipper->add_subject_polygons(_convert($safety_offset ? safety_offset($polygons) : $polygons)); return [ map Slic3r::Polygon->new(@$_), @{ $clipper->execute(CT_UNION, $jointype, $jointype) }, diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index af5c12e79e..6195a8016d 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -23,11 +23,16 @@ sub _trigger_id { $_->_trigger_layer for @{$self->regions || []}; } -sub upper_layer_slices { +sub islands { + my $self = shift; + return @{$self->slices}; +} + +sub upper_layer_islands { my $self = shift; - my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return []; - return $upper_layer->slices; + my $upper_layer = $self->object->layers->[ $self->id + 1 ] or return (); + return $upper_layer->islands; } sub region { @@ -59,20 +64,18 @@ sub make_perimeters { $_->make_perimeters for @{$self->regions}; } -sub support_islands_enclose_line { - my $self = shift; - my ($line) = @_; - return 0 if !$self->support_islands; # why can we arrive here if there are no support islands? - return (first { $_->encloses_line($line) } @{$self->support_islands}) ? 1 : 0; -} - package Slic3r::Layer::Support; use Moo; extends 'Slic3r::Layer'; # ordered collection of extrusion paths to fill surfaces for support material -has 'support_islands' => (is => 'rw'); +has 'support_islands' => (is => 'rw', default => sub { [] }); has 'support_fills' => (is => 'rw'); has 'support_interface_fills' => (is => 'rw'); +sub islands { + my $self = shift; + return @{$self->slices}, @{$self->support_islands}; +} + 1; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index dbb987e9d4..867ee6c73a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -5,7 +5,8 @@ use List::Util qw(sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(PI A B scale chained_path_items points_coincide); use Slic3r::Geometry::Clipper qw(safety_offset union_ex diff_ex intersection_ex - offset offset2 offset_ex offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection); + offset offset2 offset2_ex PFT_EVENODD union_pt traverse_pt diff intersection + union diff); use Slic3r::Surface ':types'; has 'layer' => ( @@ -131,22 +132,22 @@ sub _merge_loops { # winding order. # TODO: find a faster algorithm for this. my @loops = sort { $a->encloses_point($b->[0]) ? 0 : 1 } @$loops; # outer first - $safety_offset //= scale 0.0499; - @loops = @{ safety_offset(\@loops, $safety_offset) }; - my $expolygons = []; - while (defined (my $loop = shift @loops)) { - if ($loop->is_counter_clockwise) { - $expolygons = union_ex([ $loop, map @$_, @$expolygons ]); - } else { - $expolygons = diff_ex([ map @$_, @$expolygons ], [$loop]); - } + # we don't perform a safety offset now because it might reverse cw loops + my $slices = []; + foreach my $loop (@loops) { + $slices = $loop->is_counter_clockwise + ? union([ $loop, @$slices ]) + : diff($slices, [$loop]); } - $expolygons = offset_ex([ map @$_, @$expolygons ], -$safety_offset); + + # perform a safety offset to merge very close facets (TODO: find test case for this) + $safety_offset //= scale 0.0499; + $slices = offset2_ex($slices, +$safety_offset, -$safety_offset); Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", - scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); + scalar(@$slices), scalar(map $_->holes, @$slices), scalar(@$loops) if $Slic3r::debug; - return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$expolygons; + return map Slic3r::Surface->new(expolygon => $_, surface_type => S_TYPE_INTERNAL), @$slices; } sub make_perimeters { @@ -174,6 +175,7 @@ sub make_perimeters { # (one more than necessary so that we can detect gaps even after the desired # number of perimeters has been generated) my @last = @{$surface->expolygon}; + my @this_gaps = (); for my $i (0 .. $loop_number) { # external loop only needs half inset distance my $spacing = ($i == 0) @@ -194,7 +196,7 @@ sub make_perimeters { # won't shrink the clip polygon to be smaller than intended. offset(\@offsets, +0.5*$spacing + 2), ); - push @gaps, grep $_->area >= $gap_area_threshold, @$diff; + push @gaps, (@this_gaps = grep $_->area >= $gap_area_threshold, @$diff); } last if !@offsets || $i == $loop_number; @@ -203,16 +205,20 @@ sub make_perimeters { @last = @offsets; } + # make sure we don't infill narrow parts that are already gap-filled + # (we only consider this surface's gaps to reduce the diff() complexity) + @last = @{diff(\@last, [ map @$_, @this_gaps ])}; + # create one more offset to be used as boundary for fill # we offset by half the perimeter spacing (to get to the actual infill boundary) - # and then we offset back and forth by the infill spacing to only consider the + # and then we offset back and forth by half the infill spacing to only consider the # non-collapsing regions $self->fill_surfaces->append( @{offset2_ex( [ map $_->simplify_as_polygons(&Slic3r::SCALED_RESOLUTION), @{union_ex(\@last)} ], - -($perimeter_spacing/2 + $infill_spacing), - +$infill_spacing, - )}, + -($perimeter_spacing/2 + $infill_spacing/2), + +$infill_spacing/2, + )} ); } @@ -420,7 +426,7 @@ sub prepare_fill_surfaces { sub process_external_surfaces { my $self = shift; - my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters + my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN; my @bottom = (); foreach my $surface (grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}) { diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 138d843f2e..261c5ebaab 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -669,8 +669,9 @@ sub make_brim { for my $i (reverse 1 .. $num_loops) { # JT_SQUARE ensures no vertex is outside the given offset distance # -0.5 because islands are not represented by their centerlines - # TODO: we need the offset inwards/offset outwards logic to avoid overlapping extrusions - push @loops, @{offset2(\@islands, ($i - 1.5) * $flow->scaled_spacing, +1.0 * $flow->scaled_spacing, undef, JT_SQUARE)}; + # (first offset more, then step back - reverse order than the one used for + # perimeters because here we're offsetting outwards) + push @loops, @{offset2(\@islands, ($i + 0.5) * $flow->scaled_spacing, -1.0 * $flow->scaled_spacing, undef, JT_SQUARE)}; } @{$self->brim} = map Slic3r::ExtrusionLoop->new( @@ -720,7 +721,7 @@ sub write_gcode { # set up our extruder object my $gcodegen = Slic3r::GCode->new( config => $self->config, - extruders => $self->extruders, + extruders => $self->extruders, # we should only pass the *used* extruders (but maintain the Tx indices right!) layer_count => $self->layer_count, ); print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; @@ -756,6 +757,10 @@ sub write_gcode { } } + # always start with first extruder + # TODO: make sure we select the first *used* extruder + print $fh $gcodegen->set_extruder($self->extruders->[0]); + # calculate X,Y shift to center print around specified origin my $print_bb = $self->bounding_box; my $print_size = $print_bb->size; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index d97d588821..625f87b519 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -562,6 +562,8 @@ sub discover_horizontal_shells { Slic3r::debugf "==> DISCOVERING HORIZONTAL SHELLS\n"; + my $margin = scale &Slic3r::EXTERNAL_INFILL_MARGIN; + for my $region_id (0 .. ($self->print->regions_count-1)) { for (my $i = 0; $i < $self->layer_count; $i++) { my $layerm = $self->layers->[$i]->regions->[$region_id]; @@ -577,9 +579,13 @@ sub discover_horizontal_shells { EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { # find slices of current type for current layer - # get both slices and fill_surfaces before the former contains the perimeters area - # and the latter contains the enlarged external surfaces - my $solid = [ map $_->expolygon, grep $_->surface_type == $type, @{$layerm->slices}, @{$layerm->fill_surfaces} ]; + # use slices instead of fill_surfaces because they also include the perimeter area + # which needs to be propagated in shells; we need to grow slices like we did for + # fill_surfaces though. Using both ungrown slices and grown fill_surfaces will + # not work in some situations, as there won't be any grown region in the perimeter + # area (this was seen in a model where the top layer had one extra perimeter, thus + # its fill_surfaces was thinner than the lower layer's infill) + my $solid = [ map $_->expolygon->offset_ex($margin), grep $_->surface_type == $type, @{$layerm->slices} ]; next if !@$solid; Slic3r::debugf "Layer %d has %s surfaces\n", $i, ($type == S_TYPE_TOP) ? 'top' : 'bottom'; @@ -817,14 +823,10 @@ sub generate_support_material { } # shape of contact area - my $contact_loops = 1; - my $circle_distance = 3 * $flow->scaled_width; - my $circle; - { - # TODO: make sure teeth between circles are compatible with support material flow - my $r = 1.5 * $flow->scaled_width; - $circle = Slic3r::Polygon->new(map [ $r * cos $_, $r * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); - } + my $contact_loops = 1; + my $circle_radius = 1.5 * $flow->scaled_width; + my $circle_distance = 3 * $circle_radius; + my $circle = Slic3r::Polygon->new(map [ $circle_radius * cos $_, $circle_radius * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); # determine contact areas my %contact = (); # contact_z => [ polygons ] @@ -901,10 +903,18 @@ sub generate_support_material { ###$contact_z = $layer->print_z - $layer->height; # ignore this contact area if it's too low - next if $contact_z < $Slic3r::Config->first_layer_height; + next if $contact_z < $Slic3r::Config->get_value('first_layer_height'); $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("contact_" . $contact_z . ".svg", + expolygons => union_ex(\@contact), + red_expolygons => \@overhang, + ); + } } } my @contact_z = sort keys %contact; @@ -921,12 +931,17 @@ sub generate_support_material { # ('new' means all the areas that are lower than the last top layer # we considered) my $min_top = min(keys %top) // max(keys %contact); - push @$projection, map @{$contact{$_}}, grep { $_ > $layer->print_z && $_ < $min_top } keys %contact; + # use <= instead of just < because otherwise we'd ignore any contact regions + # having the same Z of top layers + push @$projection, map @{$contact{$_}}, grep { $_ > $layer->print_z && $_ <= $min_top } keys %contact; # now find whether any projection falls onto this top surface my $touching = intersection($projection, [ map $_->p, @top ]); if (@$touching) { - $top{ $layer->print_z } = $touching; + # grow top surfaces so that interface and support generation are generated + # with some spacing from object - it looks we don't need the actual + # top shapes so this can be done here + $top{ $layer->print_z } = [ offset($touching, $flow->scaled_spacing) ]; } # remove the areas that touched from the projection that will continue on @@ -944,17 +959,15 @@ sub generate_support_material { # if we wanted to apply some special logic to the first support layers lying on # object's top surfaces this is the place to detect them - # Let's now determine shells (interface layers) and normal support below them. - # Let's now fill each support layer by generating shells (interface layers) and - # clipping support area to the actual object boundaries. + # let's now generate interface layers below contact areas my %interface = (); # layer_id => [ polygons ] - my %support = (); # layer_id => [ polygons ] my $interface_layers = $Slic3r::Config->support_material_interface_layers; for my $layer_id (0 .. $#support_layers) { my $z = $support_layers[$layer_id]; my $this = $contact{$z} // next; + # count contact layer as interface layer - for (my $i = $layer_id; $i >= 0 && $i > $layer_id-$interface_layers; $i--) { + for (my $i = $layer_id-1; $i >= 0 && $i > $layer_id-$interface_layers; $i--) { $z = $support_layers[$i]; # Compute interface area on this layer as diff of upper contact area # (or upper interface area) and layer slices. @@ -963,29 +976,32 @@ sub generate_support_material { # surfaces before performing the diff, but this needs investigation. $this = $interface{$i} = diff( [ - @$this, - @{ $interface{$i} || [] }, + @$this, # clipped projection of the current contact regions + @{ $interface{$i} || [] }, # interface regions already applied to this layer ], [ - @{ $top{$z} || [] }, + @{ $top{$z} || [] }, # top slices on this layer + @{ $contact{$z} || [] }, # contact regions on this layer ], 1, ); } - - # determine what layers does our support belong to - for (my $i = $layer_id-$interface_layers; $i >= 0; $i--) { - $z = $support_layers[$i]; - # Compute support area on this layer as diff of upper support area - # and layer slices. - $this = $support{$i} = diff( + } + + # let's now generate support layers under interface layers + my %support = (); # layer_id => [ polygons ] + { + for my $i (reverse 0 .. $#support_layers-1) { + my $z = $support_layers[$i]; + $support{$i} = diff( [ - @$this, - @{ $support{$i} || [] }, + @{ $support{$i+1} || [] }, # support regions on upper layer + @{ $interface{$i+1} || [] }, # interface regions on upper layer ], [ - @{ $top{$z} || [] }, - @{ $interface{$i} || [] }, + @{ $top{$z} || [] }, # top slices on this layer + @{ $interface{$i} || [] }, # interface regions on this layer + @{ $contact{$z} || [] }, # contact regions on this layer ], 1, ); @@ -1024,13 +1040,30 @@ sub generate_support_material { my $process_layer = sub { my ($layer_id) = @_; - my $result = { contact => [], interface => [], support => [] }; - $contact{$layer_id} ||= []; - $interface{$layer_id} ||= []; - $support{$layer_id} ||= []; + $contact{$support_layers[$layer_id]} ||= []; + $interface{$layer_id} ||= []; + $support{$layer_id} ||= []; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("layer_" . $support_layers[$layer_id] . ".svg", + red_expolygons => union_ex($contact{$support_layers[$layer_id]}), + green_expolygons => union_ex($interface{$layer_id}), + ); + } + + # islands + my $result = { contact => [], interface => [], support => [] }; + $result->{islands} = union_ex([ + map @$_, + $interface{$layer_id}, + $support{$layer_id}, + $contact{$support_layers[$layer_id]}, + ]); # contact + my $contact_infill = []; if ((my $contact = $contact{$support_layers[$layer_id]}) && $contact_loops > 0) { my $overhang = $overhang{$support_layers[$layer_id]}; $contact = [ grep $_->is_counter_clockwise, @$contact ]; @@ -1063,11 +1096,8 @@ sub generate_support_material { [ map Slic3r::Polygon->new(@$_)->split_at_first_point, @loops ], ) }; - # subtract loops from the contact area to detect the remaining part - $interface{$layer_id} = intersection( - $interface{$layer_id}, - [ offset2(\@loops0, -($contact_loops) * $flow->scaled_spacing, +0.5*$flow->scaled_spacing) ], - ); + # add the contact infill area to the interface area + $contact_infill = [ offset2(\@loops0, -($contact_loops + 0.5) * $flow->scaled_spacing, +0.5*$flow->scaled_spacing) ]; # transform loops into ExtrusionPath objects @loops = map Slic3r::ExtrusionPath->pack( @@ -1079,14 +1109,15 @@ sub generate_support_material { $result->{contact} = [ @loops ]; } - # interface - if (@{$interface{$layer_id}}) { + # interface and contact infill + if (@{$interface{$layer_id}} || @$contact_infill) { $fillers{interface}->angle($interface_angle); # steal some space from support $interface{$layer_id} = intersection( - [ offset($interface{$layer_id}, scale 3) ], - [ @{$interface{$layer_id}}, @{$support{$layer_id}} ], + [ offset([ map @$_, $interface{$layer_id}, $contact_infill ], scale 3) ], + [ map @$_, $interface{$layer_id}, $support{$layer_id}, $contact_infill ], + undef, 1, ); $support{$layer_id} = diff( $support{$layer_id}, @@ -1094,7 +1125,7 @@ sub generate_support_material { ); my @paths = (); - foreach my $expolygon (offset_ex($interface{$layer_id}, -$flow->scaled_width/2)) { + foreach my $expolygon (@{union_ex($interface{$layer_id})}) { my @p = $fillers{interface}->fill_surface( Slic3r::Surface->new(expolygon => $expolygon), density => $interface_density, @@ -1121,7 +1152,7 @@ sub generate_support_material { my $flow_spacing = $flow->spacing; # TODO: use offset2_ex() - my $to_infill = offset_ex(union($support{$layer_id}), -$flow->scaled_width/2); + my $to_infill = union_ex($support{$layer_id}, undef, 1); my @paths = (); # base flange @@ -1161,14 +1192,19 @@ sub generate_support_material { ), @p; } - $result->{support} = [ @paths ]; + push @{$result->{support}}, @paths; } - # islands - $result->{islands} = union_ex([ - @{$interface{$layer_id} || []}, - @{$support{$layer_id} || []}, - ]); + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("islands_" . $support_layers[$layer_id] . ".svg", + red_expolygons => union_ex($contact{$support_layers[$layer_id]} || []), + green_expolygons => union_ex($interface{$layer_id} || []), + red_polylines => [ map $_->unpack->polyline, @{$result->{contact}} ], + green_polylines => [ map $_->unpack->polyline, @{$result->{interface}} ], + polylines => [ map $_->unpack->polyline, @{$result->{support}} ], + ); + } return $result; }; @@ -1183,6 +1219,7 @@ sub generate_support_material { my $support_collection = Slic3r::ExtrusionPath::Collection->new(paths => $result->{support}); $layer->support_fills($support_collection) if @{$support_collection->paths} > 0; + # TODO: use a Slic3r::ExPolygon::Collection $layer->support_islands($result->{islands}); }; Slic3r::parallelize( diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 0ac2863951..5b4d0a19b8 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -34,33 +34,36 @@ sub svg { } sub output { - my ($filename, %things) = @_; + my ($filename, @things) = @_; my $svg = svg(); + my $arrows = 1; - foreach my $type (qw(expolygons red_expolygons green_expolygons)) { - next if !$things{$type}; - my ($colour) = $type =~ /^(red|green)_/; - my $g = $svg->group( - style => { - 'stroke-width' => 2, - 'stroke' => $colour || 'black', - 'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')), - 'fill-type' => $filltype, - }, - ); - foreach my $expolygon (@{$things{$type}}) { - my $points = join ' ', map "M $_ z", map join(" ", reverse map $_->[0]*factor() . " " . $_->[1]*factor(), @$_), @$expolygon; - $g->path( - d => $points, + while (my $type = shift @things) { + my $value = shift @things; + + if ($type eq 'no_arrows') { + $arrows = 0; + } elsif ($type =~ /^(?:(.+?)_)?expolygons$/) { + my $colour = $1; + + my $g = $svg->group( + style => { + 'stroke-width' => 2, + 'stroke' => $colour || 'black', + 'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')), + 'fill-type' => $filltype, + }, ); - } - } - - foreach my $type (qw(polygons polylines white_polygons green_polygons red_polygons red_polylines)) { - if ($things{$type}) { - my $method = $type =~ /polygons/ ? 'polygon' : 'polyline'; - my ($colour) = $type =~ /^(red|green)_/; + foreach my $expolygon (@$value) { + my $points = join ' ', map "M $_ z", map join(" ", reverse map $_->[0]*factor() . " " . $_->[1]*factor(), @$_), @$expolygon; + $g->path( + d => $points, + ); + } + } elsif ($type =~ /^(?:(.+?)_)?(polygon|polyline)s$/) { + my ($colour, $method) = ($1, $2); + my $g = $svg->group( style => { 'stroke-width' => 2, @@ -68,7 +71,7 @@ sub output { 'fill' => ($type !~ /polygons/ ? 'none' : ($colour || 'grey')), }, ); - foreach my $polygon (@{$things{$type}}) { + foreach my $polygon (@$value) { my $path = $svg->get_path( 'x' => [ map($_->[X] * factor(), @$polygon) ], 'y' => [ map($_->[Y] * factor(), @$polygon) ], @@ -76,15 +79,13 @@ sub output { ); $g->$method( %$path, - 'marker-end' => $things{no_arrows} ? "" : "url(#endArrow)", + 'marker-end' => $arrows ? "" : "url(#endArrow)", ); } - } - } - - foreach my $type (qw(points red_points)) { - if ($things{$type}) { - my ($colour, $r) = $type eq 'points' ? ('black', 5) : ('red', 3); + } elsif ($type =~ /^(?:(.+?)_)?points$/) { + my $colour = $1; + my $r = $colour eq 'black' ? 5 : 3; + my $g = $svg->group( style => { 'stroke-width' => 2, @@ -92,25 +93,22 @@ sub output { 'fill' => $colour, }, ); - foreach my $point (@{$things{$type}}) { + foreach my $point (@$value) { $g->circle( cx => $point->[X] * factor(), cy => $point->[Y] * factor(), r => $r, ); } - } - } - - foreach my $type (qw(lines red_lines green_lines)) { - if ($things{$type}) { - my ($colour) = $type =~ /^(red|green)_/; + } elsif ($type =~ /^(?:(.+?)_)?lines$/) { + my $colour = $1; + my $g = $svg->group( style => { 'stroke-width' => 2, }, ); - foreach my $line (@{$things{$type}}) { + foreach my $line (@$value) { $g->line( x1 => $line->[0][X] * factor(), y1 => $line->[0][Y] * factor(), @@ -119,7 +117,7 @@ sub output { style => { 'stroke' => $colour || 'black', }, - 'marker-end' => $things{no_arrows} ? "" : "url(#endArrow)", + 'marker-end' => $arrows ? "" : "url(#endArrow)", ); } } @@ -128,26 +126,6 @@ sub output { write_svg($svg, $filename); } -sub output_points { - my ($print, $filename, $points, $red_points) = @_; - return output($print, $filename, points => $points, red_points => $red_points); -} - -sub output_polygons { - my ($print, $filename, $polygons) = @_; - return output($print, $filename, polygons => $polygons); -} - -sub output_polylines { - my ($print, $filename, $polylines) = @_; - return output($print, $filename, polylines => $polylines); -} - -sub output_lines { - my ($print, $filename, $lines) = @_; - return output($print, $filename, lines => $lines); -} - sub write_svg { my ($svg, $filename) = @_; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index c2e543467e..5aa97c9a08 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -563,7 +563,9 @@ sub horizontal_projection { } $_->make_counter_clockwise for @f; # do this after scaling, as winding order might change while doing that - return union_ex(\@f, 1); + + # the offset factor was tuned using groovemount.stl + return union_ex([ offset(\@f, Slic3r::Geometry::scale 0.01) ], 1); } 1; diff --git a/slic3r.pl b/slic3r.pl index b96b112ed1..8818c91514 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -255,6 +255,9 @@ $j --bridge-acceleration Overrides firmware's default acceleration for bridges. (mm/s^2, set zero to disable; default: $config->{bridge_acceleration}) + --first-layer-acceleration + Overrides firmware's default acceleration for first layer. (mm/s^2, set zero + to disable; default: $config->{first_layer_acceleration}) --default-acceleration Acceleration will be reset to this value after the specific settings above have been applied. (mm/s^2, set zero to disable; default: $config->{travel_speed}) diff --git a/t/fill.t b/t/fill.t index 7e2b38ede4..84678c3017 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 32; +plan tests => 33; BEGIN { use FindBin; @@ -51,17 +51,21 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $test = sub { - my ($expolygon, $flow_spacing) = @_; + my ($expolygon, $flow_spacing, $angle, $density) = @_; my $filler = Slic3r::Fill::Rectilinear->new( bounding_box => $expolygon->bounding_box, - angle => 0, + angle => $angle // 0, ); my $surface = Slic3r::Surface->new( surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, ); - my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => $flow_spacing, density => 1); + my ($params, @paths) = $filler->fill_surface( + $surface, + flow_spacing => $flow_spacing, + density => $density // 1, + ); # check whether any part was left uncovered my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths; @@ -100,6 +104,9 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] ); $test->($expolygon, 0.524341649025257); + + $expolygon = Slic3r::ExPolygon->new([ scale_points [0,0], [98,0], [98,10], [0,10] ]); + $test->($expolygon, 0.5, 45, 0.99); # non-solid infill } { diff --git a/t/shells.t b/t/shells.t index 6d4f0d27fb..1077260dcc 100644 --- a/t/shells.t +++ b/t/shells.t @@ -99,7 +99,7 @@ use Slic3r::Test; $config->set('first_layer_speed', '100%'); # prevent speed alteration $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); - $config->set('extrusion_width', 0.5); + $config->set('extrusion_width', 0.55); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); $config->set('solid_infill_speed', 99);