From 0cd10441a1c9f19f3e7a69bcf9843bbab4b6c715 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 25 Sep 2011 23:15:45 +0200 Subject: [PATCH] Optimization of travel paths for perimeters --- lib/Slic3r.pm | 1 + lib/Slic3r/ExtrusionLoop.pm | 31 ++++++++++++++++++++++++ lib/Slic3r/ExtrusionPath.pm | 19 +++++++++++++++ lib/Slic3r/Geometry.pm | 29 ++++++++++++++++++++++ lib/Slic3r/Perimeter.pm | 48 ++++--------------------------------- lib/Slic3r/Polyline.pm | 15 ++++++++++-- lib/Slic3r/Print.pm | 16 ++++++++++++- 7 files changed, 113 insertions(+), 46 deletions(-) create mode 100644 lib/Slic3r/ExtrusionLoop.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 3185c24d8..d9db1381b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -8,6 +8,7 @@ sub debugf { printf @_ if $debug; } +use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; use Slic3r::Fill; use Slic3r::Geometry; diff --git a/lib/Slic3r/ExtrusionLoop.pm b/lib/Slic3r/ExtrusionLoop.pm new file mode 100644 index 000000000..2503c77d8 --- /dev/null +++ b/lib/Slic3r/ExtrusionLoop.pm @@ -0,0 +1,31 @@ +package Slic3r::ExtrusionLoop; +use Moo; + +use XXX; + +extends 'Slic3r::Polyline::Closed'; + +sub split_at { + my $self = shift; + my ($point) = @_; + + $point = Slic3r::Point->cast($point); + + # find index of point + my $i = -1; + for (my $n = 0; $n <= $#{$self->points}; $n++) { + if ($point->id eq $self->points->[$n]->id) { + $i = $n; + last; + } + } + die "Point not found" if $i == -1; + + my @new_points = (); + push @new_points, @{$self->points}[$i .. $#{$self->points}]; + push @new_points, @{$self->points}[0 .. $i]; + + return Slic3r::ExtrusionPath->new(points => [@new_points]); +} + +1; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index 43802db9e..8df519789 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -3,4 +3,23 @@ use Moo; extends 'Slic3r::Polyline'; +sub clip_end { + my $self = shift; + my ($distance) = @_; + + while ($distance > 0) { + my $last_point = pop @{$self->points}; + + my $last_segment_length = $last_point->distance_to($self->points->[-1]); + if ($last_segment_length <= $distance) { + $distance -= $last_segment_length; + next; + } + + my $new_point = Slic3r::Geometry::point_along_segment($last_point->p, $self->points->[-1]->p, $distance); + push @{$self->points}, Slic3r::Point->cast($new_point); + $distance = 0; + } +} + 1; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 92a69716f..f81e67122 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -115,4 +115,33 @@ sub polygon_lines { return @lines; } +sub nearest_point { + my ($point, $points) = @_; + + my ($nearest_point, $distance); + foreach my $p (@$points) { + my $d = distance_between_points($point, $p); + if (!defined $distance || $d < $distance) { + $nearest_point = $p; + $distance = $d; + } + } + return $nearest_point; +} + +sub point_along_segment { + my ($p1, $p2, $distance) = @_; + + my $point = [ @$p1 ]; + + my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) ); + for (X, Y) { + if ($p1->[$_] != $p2->[$_]) { + $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length; + } + } + + return $point; +} + 1; diff --git a/lib/Slic3r/Perimeter.pm b/lib/Slic3r/Perimeter.pm index 723a9b1b0..69c49e1fd 100644 --- a/lib/Slic3r/Perimeter.pm +++ b/lib/Slic3r/Perimeter.pm @@ -58,40 +58,18 @@ sub make_perimeter { ), $self->offset_polygon($perimeters[-1]), } - # generate paths for holes + # generate paths for holes: # we start from innermost loops (that is, external ones), do them # for all holes, than go on with inner loop and do that for all - # holes and so on - foreach my $hole (map @$_, values %holes) { - my @points = @$hole; - push @points, [ @{$points[0]} ]; - # to avoid blobs, the first point is replaced by the point of - # the segment which is $Slic3r::flow_width / $Slic3r::resolution - # away from it to avoid the extruder to get two times there - $points[0] = $self->_get_point_along_line($points[0], $points[1], - $Slic3r::flow_width / $Slic3r::resolution); - push @{ $layer->perimeters }, Slic3r::ExtrusionPath->cast([@points]); - } - - # generate paths for contours + # holes and so on; + # then we generate paths for contours: # this time we do something different: we do contour loops for one # shape (that is, one original surface) at a time: we start from the # innermost loop (that is, internal one), then without interrupting # our path we go onto the outer loop and continue; this should ensure # good surface quality - foreach my $polylines (values %contours) { - my @path_points = (); - foreach my $p (map $self->_mgp_from_points_ref($_), @$polylines) { - my $points = $p->points; - # to avoid blobs, the first point is replaced by the point of - # the segment which is $Slic3r::flow_width / $Slic3r::resolution - # away from it to avoid the extruder to get two times there - push @$points, [ @{$points->[0]} ]; - $points->[0] = $self->_get_point_along_line($points->[0], $points->[1], - $Slic3r::flow_width / $Slic3r::resolution); - push @path_points, @$points; - } - push @{ $layer->perimeters }, Slic3r::ExtrusionPath->cast([ reverse @path_points ]); + foreach my $p (map @$_, values %holes, values %contours) { + push @{ $layer->perimeters }, Slic3r::ExtrusionLoop->cast($p); } # generate skirt on bottom layer @@ -158,20 +136,4 @@ sub _mgp_from_polygons_ref { return $p; } -sub _get_point_along_line { - my $self = shift; - my ($p1, $p2, $distance) = @_; - - my $point = [ @$p1 ]; - - my $line_length = sqrt( (($p2->[X] - $p1->[X])**2) + (($p2->[Y] - $p1->[Y])**2) ); - for (X, Y) { - if ($p1->[$_] != $p2->[$_]) { - $point->[$_] = $p1->[$_] + ($p2->[$_] - $p1->[$_]) * $distance / $line_length; - } - } - - return $point; -} - 1; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index c10cf21b0..afb6d90c1 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -18,11 +18,11 @@ sub id { } sub cast { - my $self = shift; + my $class = shift; my ($points) = @_; @$points = map { ref $_ eq 'ARRAY' ? Slic3r::Point->cast($_) : $_ } @$points; - return __PACKAGE__->new(points => $points); + return $class->new(points => $points); } sub lines { @@ -82,4 +82,15 @@ sub make_clockwise { $self->reverse_points if $self->is_counter_clockwise; } +sub nearest_point_to { + my $self = shift; + my ($point) = @_; + + # get point as arrayref + $point = ref $point eq 'ARRAY' ? $point : $point->p; + + $point = Slic3r::Geometry::nearest_point($point, $self->p); + return Slic3r::Point->cast($point); +} + 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index dc9f7ac73..cd2fedcce 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -183,6 +183,7 @@ sub export_gcode { # make up a subroutine to generate G1 commands my $extrusion_distance = 0; + my $last_pos; # on XY plane my $G1 = sub { my ($point, $z, $e, $comment) = @_; printf $fh "G1"; @@ -191,6 +192,7 @@ sub export_gcode { printf $fh " X%.${dec}f Y%.${dec}f", ($point->x * $Slic3r::resolution) + $shift[X], ($point->y * $Slic3r::resolution) + $shift[Y]; #** + $last_pos = $point->p; } if ($z) { printf $fh " Z%.${dec}f", $z; @@ -269,7 +271,19 @@ sub export_gcode { $Extrude->($_, 'skirt') for @{ $layer->skirts }; # extrude perimeters - $Extrude->($_, 'perimeter') for @{ $layer->perimeters }; + for my $loop (@{ $layer->perimeters }) { + # find the point of the loop that is closest to the current extruder position + my $start_at = $last_pos ? $loop->nearest_point_to($last_pos) : $loop->points->[0]; + + # split the loop at the starting point and make a path + my $extrusion_path = $loop->split_at($start_at); + + # clip the path to avoid the extruder to get exactly on the first point of the loop + $extrusion_path->clip_end($Slic3r::flow_width / $Slic3r::resolution); + + # extrude along the path + $Extrude->($extrusion_path, 'perimeter') + } # extrude fills $Extrude->($_, 'fill') for @{ $layer->fills };