diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 9b629ed98..f936e0f60 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -53,6 +53,7 @@ our $flow_width; # print options our $perimeter_offsets = 3; our $solid_layers = 3; +our $fill_type = 'rectilinear'; our $fill_density = 0.4; # 1 = 100% our $fill_angle = 0; our $temperature = 200; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index e816e72bf..5193e9e71 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -1,6 +1,60 @@ package Slic3r::Fill; use Moo; +use Slic3r::Fill::Base; use Slic3r::Fill::Rectilinear; +has 'print' => (is => 'ro', required => 1); +has 'fillers' => (is => 'rw', default => sub { {} }); + +our %FillTypes = ( + rectilinear => 'Slic3r::Fill::Rectilinear', +); + +sub BUILD { + my $self = shift; + $self->fillers->{$_} ||= $FillTypes{$_}->new(print => $self->print) + for ('rectilinear', $Slic3r::fill_type); +} + +sub make_fill { + my $self = shift; + my ($layer) = @_; + + my $max_print_dimension = $self->print->max_length * sqrt(2); + for (values %{$self->fillers}) { + $_->layer($layer); + $_->max_print_dimension($max_print_dimension); + } + + printf "Filling layer %d:\n", $layer->id; + foreach my $surface_collection (@{ $layer->fill_surfaces }) { + my @path_collection = (); + + SURFACE: foreach my $surface (@{ $surface_collection->surfaces }) { + Slic3r::debugf " Processing surface %s:\n", $surface->id; + + my $filler = $Slic3r::fill_type; + my $density = $Slic3r::fill_density; + next SURFACE unless $density > 0; + + # force 100% density and rectilinear fill for external surfaces + if ($surface->surface_type ne 'internal') { + $density = 1; + $filler = 'rectilinear'; + } + + push @path_collection, $self->fillers->{$filler}->fill_surface($surface, + density => $density, + ); + } + + # save into layer + push @{ $layer->fills }, Slic3r::ExtrusionPath::Collection->new( + paths => [ map Slic3r::ExtrusionPath->cast([ @$_ ]), @path_collection ], + ); + $layer->fills->[-1]->cleanup; + } +} + 1; diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm new file mode 100644 index 000000000..0e685b94a --- /dev/null +++ b/lib/Slic3r/Fill/Base.pm @@ -0,0 +1,56 @@ +package Slic3r::Fill::Base; +use Moo; + +use XXX; + +has 'layer' => (is => 'rw'); +has 'max_print_dimension' => (is => 'rw'); + +use constant PI => 4 * atan2(1, 1); + +sub infill_direction { + my $self = shift; + my ($polygons) = @_; + + # set infill angle + my (@rotate, @shift); + $rotate[0] = Slic3r::Geometry::deg2rad($Slic3r::fill_angle); + $rotate[1] = [ $self->max_print_dimension / 2, $self->max_print_dimension / 2 ]; + @shift = @{$rotate[1]}; + + # alternate fill direction + if ($self->layer->id % 2) { + $rotate[0] = Slic3r::Geometry::deg2rad($Slic3r::fill_angle) + PI/2; + } + + # TODO: here we should implement an "infill in direction of bridges" option + + @shift = @{ +(Slic3r::Geometry::rotate_points(@rotate, \@shift))[0] }; + return [\@rotate, \@shift]; +} + +sub rotate_points { + my $self = shift; + my ($polygons, $rotate_vector) = @_; + my @rotate = @{$rotate_vector->[0]}; + my @shift = @{$rotate_vector->[1]}; + + # rotate surface as needed + @$polygons = map [ Slic3r::Geometry::move_points(\@shift, @$_) ], + map [ Slic3r::Geometry::rotate_points(@rotate, @$_) ], @$polygons if $rotate[0]; + +} + +sub rotate_points_back { + my $self = shift; + my ($paths, $rotate_vector) = @_; + my @rotate = @{$rotate_vector->[0]}; + my @shift = @{$rotate_vector->[1]}; + + if ($rotate[0]) { + @$paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ], + map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @$paths; + } +} + +1; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index b5a026b13..30a6352e1 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -1,7 +1,8 @@ package Slic3r::Fill::Rectilinear; use Moo; -use constant PI => 4 * atan2(1, 1); +extends 'Slic3r::Fill::Base'; + use constant X1 => 0; use constant Y1 => 1; use constant X2 => 2; @@ -11,170 +12,131 @@ use constant B => 1; use constant X => 0; use constant Y => 1; -use POSIX qw(ceil); use XXX; -sub make_fill { +sub fill_surface { my $self = shift; - my ($print, $layer) = @_; - printf "Filling layer %d:\n", $layer->id; + my ($surface, %params) = @_; - my $max_print_dimension = $print->max_length * sqrt(2); + my $polygons = [ $surface->p ]; - my $n = 1; - foreach my $surface_collection (@{ $layer->fill_surfaces }) { - my @path_collection = (); + # rotate polygons so that we can work with vertical lines here + my $rotate_vector = $self->infill_direction($polygons); + $self->rotate_points($polygons, $rotate_vector); + + my $distance_between_lines = $Slic3r::flow_width / $Slic3r::resolution / $params{density}; + my $number_of_lines = int(0.99999999 + $self->max_print_dimension / $distance_between_lines); # ceil + + #printf "distance = %f\n", $distance_between_lines; + #printf "number_of_lines = %d\n", $number_of_lines; + + # this arrayref will hold intersection points of the fill grid with surface segments + my $points = [ map [], 0..$number_of_lines-1 ]; + foreach my $line (map Slic3r::Geometry::polygon_lines($_), @$polygons) { - SURFACE: foreach my $surface (@{ $surface_collection->surfaces }) { - Slic3r::debugf " Processing surface %s:\n", $surface->id; - my $polygons = [ $surface->p ]; + # find out the coordinates + my @coordinates = map @$_, @$line; + + # get the extents of the segment along the primary axis + my @line_c = sort { $a <=> $b } @coordinates[X1, X2]; + Slic3r::debugf "Segment %d,%d - %d,%d (extents: %f, %f)\n", @coordinates, @line_c; + + for (my $c = int($line_c[0] / $distance_between_lines) * $distance_between_lines; + $c <= $line_c[1]; $c += $distance_between_lines) { + next if $c < $line_c[0] || $c > $line_c[1]; + my $i = sprintf('%.0f', $c / $distance_between_lines) - 1; + #printf "CURRENT \$i = %d, \$c = %f\n", $i, $c; - # set infill angle - my (@rotate, @shift); - $rotate[0] = Slic3r::Geometry::deg2rad($Slic3r::fill_angle); - $rotate[1] = [ $max_print_dimension / 2, $max_print_dimension / 2 ]; - @shift = @{$rotate[1]}; - - # alternate fill direction - if ($layer->id % 2) { - $rotate[0] = Slic3r::Geometry::deg2rad($Slic3r::fill_angle) + PI/2; + # if the segment is parallel to our ray, there will be two intersection points + if ($line_c[0] == $line_c[1]) { + Slic3r::debugf " Segment is parallel!\n"; + push @{ $points->[$i] }, $coordinates[Y1], $coordinates[Y2]; + Slic3r::debugf " intersections at %f (%d) = %f, %f\n", $c, $i, $points->[$i][-2], $points->[$i][-1]; + } else { + Slic3r::debugf " Segment NOT parallel!\n"; + # one point of intersection + push @{ $points->[$i] }, $coordinates[Y1] + ($coordinates[Y2] - $coordinates[Y1]) + * ($c - $coordinates[X1]) / ($coordinates[X2] - $coordinates[X1]); + Slic3r::debugf " intersection at %f (%d) = %f\n", $c, $i, $points->[$i][-1]; } - - # TODO: here we should implement an "infill in direction of bridges" option - - # rotate surface as needed - @shift = @{ +(Slic3r::Geometry::rotate_points(@rotate, \@shift))[0] }; - @$polygons = map [ Slic3r::Geometry::move_points(\@shift, @$_) ], - map [ Slic3r::Geometry::rotate_points(@rotate, @$_) ], @$polygons if $rotate[0]; - - # force 100% density for external surfaces - my $density = $surface->surface_type eq 'internal' ? $Slic3r::fill_density : 1; - next SURFACE unless $density > 0; - - my $distance_between_lines = $Slic3r::flow_width / $Slic3r::resolution / $density; - my $number_of_lines = ceil($max_print_dimension / $distance_between_lines); - - #printf "distance = %f\n", $distance_between_lines; - #printf "number_of_lines = %d\n", $number_of_lines; - - # this arrayref will hold intersection points of the fill grid with surface segments - my $points = [ map [], 0..$number_of_lines-1 ]; - foreach my $line (map Slic3r::Geometry::polygon_lines($_), @$polygons) { - - # find out the coordinates - my @coordinates = map @$_, @$line; - - # get the extents of the segment along the primary axis - my @line_c = sort { $a <=> $b } @coordinates[X1, X2]; - Slic3r::debugf "Segment %d,%d - %d,%d (extents: %f, %f)\n", @coordinates, @line_c; - - for (my $c = int($line_c[0] / $distance_between_lines) * $distance_between_lines; - $c <= $line_c[1]; $c += $distance_between_lines) { - next if $c < $line_c[0] || $c > $line_c[1]; - my $i = sprintf('%.0f', $c / $distance_between_lines) - 1; - #printf "CURRENT \$i = %d, \$c = %f\n", $i, $c; - - # if the segment is parallel to our ray, there will be two intersection points - if ($line_c[0] == $line_c[1]) { - Slic3r::debugf " Segment is parallel!\n"; - push @{ $points->[$i] }, $coordinates[Y1], $coordinates[Y2]; - Slic3r::debugf " intersections at %f (%d) = %f, %f\n", $c, $i, $points->[$i][-2], $points->[$i][-1]; - } else { - Slic3r::debugf " Segment NOT parallel!\n"; - # one point of intersection - push @{ $points->[$i] }, $coordinates[Y1] + ($coordinates[Y2] - $coordinates[Y1]) - * ($c - $coordinates[X1]) / ($coordinates[X2] - $coordinates[X1]); - Slic3r::debugf " intersection at %f (%d) = %f\n", $c, $i, $points->[$i][-1]; - } - } - } - - # sort and remove duplicates - for (my $i = 0; $i <= $#$points; $i++) { - my %h = map { sprintf("%.9f", $_) => 1 } @{ $points->[$i] }; - $points->[$i] = [ sort { $a <=> $b } keys %h ]; - } - - # generate extrusion paths - my (@paths, @path_points) = (); - my $direction = 0; - - my $stop_path = sub { - # defensive programming - if (@path_points == 1) { - #warn "There shouldn't be only one point in the current path"; - } - - # if we were constructing a path, stop it - push @paths, [ @path_points ] if @path_points > 1; - @path_points = (); - }; - - # loop until we have spare points - CYCLE: while (scalar map(@$_, @$points) > 1) { - # loop through rows - ROW: for (my $i = 0; $i <= $#$points; $i++) { - my $row = $points->[$i] or next ROW; - Slic3r::debugf "\nProcessing row %d (direction: %d)...\n", $i, $direction; - if (!@$row) { - Slic3r::debugf " no points\n"; - $stop_path->(); - next ROW; - } - Slic3r::debugf " points = %s\n", join ', ', @$row if $Slic3r::debug; - - # coordinate of current row - my $c = ($i + 1) * $distance_between_lines; - - # need to start a path? - if (!@path_points) { - Slic3r::debugf " path starts at %d\n", $row->[0]; - push @path_points, [ $c, shift @$row ]; - } - - my @search_points = @$row; - @search_points = reverse @search_points if $direction == 1; - my @connectable_points = $self->find_connectable_points($polygons, $path_points[-1], $c, [@search_points]); - Slic3r::debugf " ==> found %d connectable points = %s\n", scalar(@connectable_points), - join ', ', @connectable_points if $Slic3r::debug; - - if (!@connectable_points && @path_points && $path_points[-1][0] != $c) { - # no connectable in this row - $stop_path->(); - } - - if (@connectable_points == 1 && $path_points[0][0] != $c - && (($connectable_points[0] == $row->[-1] && $direction == 0) - || ($connectable_points[0] == $row->[0] && $direction == 1))) { - $i--; # keep searching on current row in the opposite direction - } - - foreach my $p (@connectable_points) { - push @path_points, [ $c, $p ]; - @$row = grep $_ != $p, @$row; # remove point from row - } - - # invert direction - $direction = $direction ? 0 : 1; - } - $stop_path->() if @path_points; - } - - # paths must be rotated back - if ($rotate[0]) { - @paths = map [ Slic3r::Geometry::rotate_points(-$rotate[0], $rotate[1], @$_) ], - map [ Slic3r::Geometry::move_points([map -$_, @shift], @$_) ], @paths; - } - - push @path_collection, @paths; } - - # save into layer - push @{ $layer->fills }, Slic3r::ExtrusionPath::Collection->new( - paths => [ map Slic3r::ExtrusionPath->cast([ @$_ ]), @path_collection ], - ); - $layer->fills->[-1]->cleanup; } + + # sort and remove duplicates + for (my $i = 0; $i <= $#$points; $i++) { + my %h = map { sprintf("%.9f", $_) => 1 } @{ $points->[$i] }; + $points->[$i] = [ sort { $a <=> $b } keys %h ]; + } + + # generate extrusion paths + my (@paths, @path_points) = (); + my $direction = 0; + + my $stop_path = sub { + # defensive programming + if (@path_points == 1) { + #warn "There shouldn't be only one point in the current path"; + } + + # if we were constructing a path, stop it + push @paths, [ @path_points ] if @path_points > 1; + @path_points = (); + }; + + # loop until we have spare points + CYCLE: while (scalar map(@$_, @$points) > 1) { + # loop through rows + ROW: for (my $i = 0; $i <= $#$points; $i++) { + my $row = $points->[$i] or next ROW; + Slic3r::debugf "\nProcessing row %d (direction: %d)...\n", $i, $direction; + if (!@$row) { + Slic3r::debugf " no points\n"; + $stop_path->(); + next ROW; + } + Slic3r::debugf " points = %s\n", join ', ', @$row if $Slic3r::debug; + + # coordinate of current row + my $c = ($i + 1) * $distance_between_lines; + + # need to start a path? + if (!@path_points) { + Slic3r::debugf " path starts at %d\n", $row->[0]; + push @path_points, [ $c, shift @$row ]; + } + + my @search_points = @$row; + @search_points = reverse @search_points if $direction == 1; + my @connectable_points = $self->find_connectable_points($polygons, $path_points[-1], $c, [@search_points]); + Slic3r::debugf " ==> found %d connectable points = %s\n", scalar(@connectable_points), + join ', ', @connectable_points if $Slic3r::debug; + + if (!@connectable_points && @path_points && $path_points[-1][0] != $c) { + # no connectable in this row + $stop_path->(); + } + + if (@connectable_points == 1 && $path_points[0][0] != $c + && (($connectable_points[0] == $row->[-1] && $direction == 0) + || ($connectable_points[0] == $row->[0] && $direction == 1))) { + $i--; # keep searching on current row in the opposite direction + } + + foreach my $p (@connectable_points) { + push @path_points, [ $c, $p ]; + @$row = grep $_ != $p, @$row; # remove point from row + } + + # invert direction + $direction = $direction ? 0 : 1; + } + $stop_path->() if @path_points; + } + + # paths must be rotated back + $self->rotate_points_back(\@paths, $rotate_vector); + + return @paths; } # this function will select the first contiguous block of diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 51a82d8ba..1b2691727 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -169,10 +169,10 @@ sub extrude_perimeters { sub extrude_fills { my $self = shift; - my $fill_extruder = Slic3r::Fill::Rectilinear->new; + my $fill_extruder = Slic3r::Fill->new('print' => $self); foreach my $layer (@{ $self->layers }) { - $fill_extruder->make_fill($self, $layer); + $fill_extruder->make_fill($layer); Slic3r::debugf " generated %d paths: %s\n", scalar @{ $layer->fills }, join ' ', map $_->id, map @{$_->paths}, @{ $layer->fills } if $Slic3r::debug;