From 5daaf454b1c60f4ccf7d9f760e798156cdf09d7d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 6 Oct 2011 15:24:21 +0200 Subject: [PATCH] Faster algorithm for rectilinear fill --- lib/Slic3r.pm | 2 +- lib/Slic3r/Config.pm | 4 + lib/Slic3r/Extruder.pm | 14 +-- lib/Slic3r/Fill.pm | 4 +- lib/Slic3r/Fill/Rectilinear.pm | 139 +++------------------------- lib/Slic3r/Fill/Rectilinear2.pm | 159 ++++++++++++++++++++++++++++++++ lib/Slic3r/Geometry.pm | 18 ++-- lib/Slic3r/SVG.pm | 8 +- slic3r.pl | 1 + t/polyclip.t | 52 +++++++++-- 10 files changed, 242 insertions(+), 159 deletions(-) create mode 100644 lib/Slic3r/Fill/Rectilinear2.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f936e0f60..0faac55b2 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -62,7 +62,7 @@ our $temperature = 200; our $retract_length = 1; # mm our $retract_restart_extra = 0; # mm our $retract_speed = 40; # mm/sec -our $retract_before_travel = 1; # mm +our $retract_before_travel = 2; # mm # skirt options our $skirts = 1; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 8a065eae9..b93e11063 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -218,6 +218,10 @@ sub validate { $Slic3r::print_center = [ split /,/, $Slic3r::print_center ] if !ref $Slic3r::print_center; + # --fill-type + die "Invalid value for --fill-type\n" + if !exists $Slic3r::Fill::FillTypes{$Slic3r::fill_type}; + # --fill-density die "Invalid value for --fill-density\n" if $Slic3r::fill_density < 0 || $Slic3r::fill_density > 1; diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 3c8cd2bb3..e740bfb51 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -69,15 +69,11 @@ sub extrude { my $gcode = ""; - # reset extrusion distance counter - if (!$Slic3r::use_relative_e_distances) { - $self->extrusion_distance(0); - $gcode .= "G92 E0 ; reset extrusion distance\n"; - } - - # retract - if (Slic3r::Geometry::distance_between_points($self->last_pos, $path->points->[0]->p) * $Slic3r::resolution - >= $Slic3r::retract_before_travel) { + # retract if distance from previous position is greater or equal to the one + # specified by the user *and* to the maximum distance between infill lines + my $distance_from_last_pos = Slic3r::Geometry::distance_between_points($self->last_pos, $path->points->[0]->p) * $Slic3r::resolution; + if ($distance_from_last_pos >= $Slic3r::retract_before_travel + && $distance_from_last_pos >= $Slic3r::flow_width / $Slic3r::fill_density * sqrt(2)) { $gcode .= $self->retract; } diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 5193e9e71..f922d7063 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -3,12 +3,14 @@ use Moo; use Slic3r::Fill::Base; use Slic3r::Fill::Rectilinear; +use Slic3r::Fill::Rectilinear2; has 'print' => (is => 'ro', required => 1); has 'fillers' => (is => 'rw', default => sub { {} }); our %FillTypes = ( - rectilinear => 'Slic3r::Fill::Rectilinear', + rectilinear => 'Slic3r::Fill::Rectilinear', + rectilinear2 => 'Slic3r::Fill::Rectilinear2', ); sub BUILD { diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 30a6352e1..e834b866c 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -7,10 +7,6 @@ use constant X1 => 0; use constant Y1 => 1; use constant X2 => 2; use constant Y2 => 3; -use constant A => 0; -use constant B => 1; -use constant X => 0; -use constant Y => 1; use XXX; @@ -18,119 +14,23 @@ sub fill_surface { my $self = shift; my ($surface, %params) = @_; - my $polygons = [ $surface->p ]; - # rotate polygons so that we can work with vertical lines here + my $polygons = [ $surface->p ]; my $rotate_vector = $self->infill_direction($polygons); $self->rotate_points($polygons, $rotate_vector); + my $bounding_box = [ Slic3r::Geometry::bounding_box(map @$_, $polygons) ]; + my $surface_width = $bounding_box->[X2] - $bounding_box->[X1]; + my $surface_height = $bounding_box->[Y2] - $bounding_box->[Y1]; + 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) { - - # 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; + my @paths = (); + my $x = $bounding_box->[X1]; + while ($x < $bounding_box->[X2]) { + my $vertical_line = [ [$x, $bounding_box->[Y2]], [$x, $bounding_box->[Y1]] ]; + push @paths, @{ Slic3r::Geometry::clip_segment_complex_polygon($vertical_line, $polygons) }; + $x += int($distance_between_lines); } # paths must be rotated back @@ -139,21 +39,4 @@ sub fill_surface { return @paths; } -# this function will select the first contiguous block of -# points connectable to a given one -sub find_connectable_points { - my $self = shift; - my ($polygons, $point, $c, $points) = @_; - - my @connectable_points = (); - foreach my $p (@$points) { - if (!Slic3r::Geometry::can_connect_points($point, [ $c, $p ], $polygons)) { - @connectable_points ? last : next; - } - push @connectable_points, $p; - $point = [ $c, $p ] if $point->[0] != $c; - } - return @connectable_points; -} - 1; diff --git a/lib/Slic3r/Fill/Rectilinear2.pm b/lib/Slic3r/Fill/Rectilinear2.pm new file mode 100644 index 000000000..e96c61fa0 --- /dev/null +++ b/lib/Slic3r/Fill/Rectilinear2.pm @@ -0,0 +1,159 @@ +package Slic3r::Fill::Rectilinear2; +use Moo; + +extends 'Slic3r::Fill::Base'; + +use constant X1 => 0; +use constant Y1 => 1; +use constant X2 => 2; +use constant Y2 => 3; +use constant A => 0; +use constant B => 1; +use constant X => 0; +use constant Y => 1; + +use XXX; + +sub fill_surface { + my $self = shift; + my ($surface, %params) = @_; + + my $polygons = [ $surface->p ]; + + # 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) { + + # 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 + $self->rotate_points_back(\@paths, $rotate_vector); + + return @paths; +} + +# this function will select the first contiguous block of +# points connectable to a given one +sub find_connectable_points { + my $self = shift; + my ($polygons, $point, $c, $points) = @_; + + my @connectable_points = (); + foreach my $p (@$points) { + if (!Slic3r::Geometry::can_connect_points($point, [ $c, $p ], $polygons)) { + @connectable_points ? last : next; + } + push @connectable_points, $p; + $point = [ $c, $p ] if $point->[0] != $c; + } + return @connectable_points; +} + +1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 89c87e545..fb6540990 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -316,15 +316,10 @@ sub polygon_points_visibility { return 1; } -my $i = 0; sub line_intersection { my ($line1, $line2, $require_crossing) = @_; $require_crossing ||= 0; - Slic3r::SVG::output(undef, "line_intersection_" . $i++ . ".svg", - lines => [ $line1, $line2 ], - ) if 0; - my $intersection = _line_intersection(map @$_, @$line1, @$line2); return (ref $intersection && $intersection->[1] == $require_crossing) ? $intersection->[0] @@ -460,16 +455,23 @@ sub clip_segment_complex_polygon { my ($line, $polygons) = @_; my @intersections = grep $_, map line_intersection($line, $_, 1), - map polygon_lines($_), @$polygons; + map polygon_lines($_), @$polygons or return (); - @intersections = sort { "$a->[X],$a->[Y]" cmp "$b->[X],$b->[Y]" } @intersections; + # this is not very elegant, however it works + @intersections = sort { sprintf("%020f,%020f", @$a) cmp sprintf("%020f,%020f", @$b) } @intersections; shift(@intersections) if !grep(point_in_polygon($intersections[0], $_), @$polygons) && !grep(polygon_segment_having_point($_, $intersections[0]), @$polygons); + # defensive programming + die "Invalid intersections" if @intersections % 2 != 0; + my @lines = (); while (@intersections) { - push @lines, [ shift(@intersections), shift(@intersections) ]; + # skip tangent points + my @points = map shift @intersections, 1..2; + next if points_coincide(@points); + push @lines, [ @points ]; } return [@lines]; } diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 6ce81efaa..0cc53ca2f 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -22,14 +22,14 @@ sub output { my $svg = svg($print); - foreach my $type (qw(polygons polylines)) { + foreach my $type (qw(polygons polylines white_polygons red_polylines)) { if ($things{$type}) { - my $method = $type eq 'polygons' ? 'polygon' : 'polyline'; + my $method = $type =~ /polygons/ ? 'polygon' : 'polyline'; my $g = $svg->group( style => { 'stroke-width' => 2, - 'stroke' => 'black', - 'fill' => 'none', + 'stroke' => $type =~ /red_/ ? 'red' : 'black', + 'fill' => $type eq 'polygons' ? 'grey' : 'none', }, ); foreach my $polygon (@{$things{$type}}) { diff --git a/slic3r.pl b/slic3r.pl index 182ef1bcd..22f89e9ad 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -44,6 +44,7 @@ GetOptions( # print options 'perimeters=i' => \$Slic3r::perimeter_offsets, 'solid-layers=i' => \$Slic3r::solid_layers, + 'fill-type=s' => \$Slic3r::fill_type, 'fill-density=f' => \$Slic3r::fill_density, 'fill-angle=i' => \$Slic3r::fill_angle, 'temperature=i' => \$Slic3r::temperature, diff --git a/t/polyclip.t b/t/polyclip.t index 0a66565fe..f4366d4ce 100644 --- a/t/polyclip.t +++ b/t/polyclip.t @@ -1,14 +1,26 @@ use Test::More; -plan tests => 10; +plan tests => 13; BEGIN { use FindBin; use lib "$FindBin::Bin/../lib"; } +use Math::Clipper qw(is_counter_clockwise); use Slic3r; +#========================================================== + +is Slic3r::Geometry::point_in_segment([10, 10], [ [5, 10], [20, 10] ]), 1, 'point in horizontal segment'; +is Slic3r::Geometry::point_in_segment([30, 10], [ [5, 10], [20, 10] ]), 0, 'point not in horizontal segment'; +is Slic3r::Geometry::point_in_segment([10, 10], [ [10, 5], [10, 20] ]), 1, 'point in vertical segment'; +is Slic3r::Geometry::point_in_segment([10, 30], [ [10, 5], [10, 20] ]), 0, 'point not in vertical segment'; +is Slic3r::Geometry::point_in_segment([15, 15], [ [10, 10], [20, 20] ]), 1, 'point in diagonal segment'; +is Slic3r::Geometry::point_in_segment([20, 15], [ [10, 10], [20, 20] ]), 0, 'point not in diagonal segment'; + +#========================================================== + my $square = [ # ccw [10, 10], [20, 10], @@ -52,11 +64,35 @@ is_deeply $intersections, [ #========================================================== -is Slic3r::Geometry::point_in_segment([10, 10], [ [5, 10], [20, 10] ]), 1, 'point in horizontal segment'; -is Slic3r::Geometry::point_in_segment([30, 10], [ [5, 10], [20, 10] ]), 0, 'point not in horizontal segment'; -is Slic3r::Geometry::point_in_segment([10, 10], [ [10, 5], [10, 20] ]), 1, 'point in vertical segment'; -is Slic3r::Geometry::point_in_segment([10, 30], [ [10, 5], [10, 20] ]), 0, 'point not in vertical segment'; -is Slic3r::Geometry::point_in_segment([15, 15], [ [10, 10], [20, 20] ]), 1, 'point in diagonal segment'; -is Slic3r::Geometry::point_in_segment([20, 15], [ [10, 10], [20, 20] ]), 0, 'point not in diagonal segment'; +my $large_circle = [ # ccw + [151.8639,288.1192], [133.2778,284.6011], [115.0091,279.6997], [98.2859,270.8606], [82.2734,260.7933], + [68.8974,247.4181], [56.5622,233.0777], [47.7228,216.3558], [40.1617,199.0172], [36.6431,180.4328], + [34.932,165.2312], [37.5567,165.1101], [41.0547,142.9903], [36.9056,141.4295], [40.199,124.1277], + [47.7776,106.7972], [56.6335,90.084], [68.9831,75.7557], [82.3712,62.3948], [98.395,52.3429], + [115.1281,43.5199], [133.4004,38.6374], [151.9884,35.1378], [170.8905,35.8571], [189.6847,37.991], + [207.5349,44.2488], [224.8662,51.8273], [240.0786,63.067], [254.407,75.4169], [265.6311,90.6406], + [275.6832,106.6636], [281.9225,124.52], [286.8064,142.795], [287.5061,161.696], [286.7874,180.5972], + [281.8856,198.8664], [275.6283,216.7169], [265.5604,232.7294], [254.3211,247.942], [239.9802,260.2776], + [224.757,271.5022], [207.4179,279.0635], [189.5605,285.3035], [170.7649,287.4188], +]; +is is_counter_clockwise($large_circle), 1, "contour is counter-clockwise"; + +my $small_circle = [ # cw + [158.227,215.9007], [164.5136,215.9007], [175.15,214.5007], [184.5576,210.6044], [190.2268,207.8743], + [199.1462,201.0306], [209.0146,188.346], [213.5135,177.4829], [214.6979,168.4866], [216.1025,162.3325], + [214.6463,151.2703], [213.2471,145.1399], [209.0146,134.9203], [199.1462,122.2357], [189.8944,115.1366], + [181.2504,111.5567], [175.5684,108.8205], [164.5136,107.3655], [158.2269,107.3655], [147.5907,108.7656], + [138.183,112.6616], [132.5135,115.3919], [123.5943,122.2357], [113.7259,134.92], [109.2269,145.7834], + [108.0426,154.7799], [106.638,160.9339], [108.0941,171.9957], [109.4933,178.1264], [113.7259,188.3463], + [123.5943,201.0306], [132.8461,208.1296], [141.4901,211.7094], [147.172,214.4458], +]; +is is_counter_clockwise($small_circle), 0, "hole is clockwise"; + +$line = [ [152.741724,288.086671142818], [152.741724,34.166466971035] ]; + +my $intersections = Slic3r::Geometry::clip_segment_complex_polygon($line, [ $large_circle, $small_circle ]); +is_deeply $intersections, [ + [ [152.741724, 35.166466971035], [152.741724, 108.087543109156] ], + [ [152.741724, 215.178806915206], [152.741724, 288.086671142818] ], +], 'line is clipped to square with hole'; -#==========================================================