From 5f17fa342b99e14f21e7edf722d01b65a7bc167d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci <aar@cpan.org> Date: Wed, 28 Aug 2013 19:50:16 +0200 Subject: [PATCH] Move arc fitting code to its own post-processing filter and remove the built-in ExtrusionPath::Arc class --- MANIFEST | 1 - lib/Slic3r.pm | 2 +- lib/Slic3r/ExtrusionPath.pm | 112 --------------------- lib/Slic3r/ExtrusionPath/Arc.pm | 34 ------- lib/Slic3r/ExtrusionPath/Collection.pm | 6 -- lib/Slic3r/GCode.pm | 43 +------- lib/Slic3r/GCode/ArcFitting.pm | 133 +++++++++++++++++++++++++ lib/Slic3r/GCode/Layer.pm | 13 +++ t/arcs.t | 1 + 9 files changed, 149 insertions(+), 196 deletions(-) delete mode 100644 lib/Slic3r/ExtrusionPath/Arc.pm create mode 100644 lib/Slic3r/GCode/ArcFitting.pm diff --git a/MANIFEST b/MANIFEST index 7923aee40..aa795023e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -5,7 +5,6 @@ lib/Slic3r/ExPolygon.pm lib/Slic3r/Extruder.pm lib/Slic3r/ExtrusionLoop.pm lib/Slic3r/ExtrusionPath.pm -lib/Slic3r/ExtrusionPath/Arc.pm lib/Slic3r/ExtrusionPath/Collection.pm lib/Slic3r/Fill.pm lib/Slic3r/Fill/ArchimedeanChords.pm diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 87fb47e7c..70afe560b 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -42,7 +42,6 @@ use Slic3r::ExPolygon; use Slic3r::Extruder; use Slic3r::ExtrusionLoop; use Slic3r::ExtrusionPath; -use Slic3r::ExtrusionPath::Arc; use Slic3r::ExtrusionPath::Collection; use Slic3r::Fill; use Slic3r::Flow; @@ -50,6 +49,7 @@ use Slic3r::Format::AMF; use Slic3r::Format::OBJ; use Slic3r::Format::STL; use Slic3r::GCode; +use Slic3r::GCode::ArcFitting; use Slic3r::GCode::CoolingBuffer; use Slic3r::GCode::Layer; use Slic3r::GCode::MotionPlanner; diff --git a/lib/Slic3r/ExtrusionPath.pm b/lib/Slic3r/ExtrusionPath.pm index b0e0fc999..0740f750f 100644 --- a/lib/Slic3r/ExtrusionPath.pm +++ b/lib/Slic3r/ExtrusionPath.pm @@ -126,116 +126,4 @@ sub split_at_acute_angles { return @paths; } -sub detect_arcs { - my $self = shift; - my ($max_angle, $len_epsilon) = @_; - - $max_angle = deg2rad($max_angle || 15); - $len_epsilon ||= 10 / &Slic3r::SCALING_FACTOR; - my $parallel_degrees_limit = abs(Slic3r::Geometry::deg2rad(3)); - - my @points = @{$self->points}; - my @paths = (); - - # we require at least 3 consecutive segments to form an arc - CYCLE: while (@points >= 4) { - POINT: for (my $i = 0; $i <= $#points - 3; $i++) { - my $s1 = Slic3r::Line->new($points[$i], $points[$i+1]); - my $s2 = Slic3r::Line->new($points[$i+1], $points[$i+2]); - my $s3 = Slic3r::Line->new($points[$i+2], $points[$i+3]); - my $s1_len = $s1->length; - my $s2_len = $s2->length; - my $s3_len = $s3->length; - - # segments must have the same length - if (abs($s3_len - $s2_len) > $len_epsilon) { - # optimization: skip a cycle - $i++; - next; - } - next if abs($s2_len - $s1_len) > $len_epsilon; - - # segments must have the same relative angle - my $s1_angle = $s1->atan; - my $s2_angle = $s2->atan; - my $s3_angle = $s3->atan; - $s1_angle += 2*PI if $s1_angle < 0; - $s2_angle += 2*PI if $s2_angle < 0; - $s3_angle += 2*PI if $s3_angle < 0; - my $s1s2_angle = $s2_angle - $s1_angle; - my $s2s3_angle = $s3_angle - $s2_angle; - next if abs($s1s2_angle - $s2s3_angle) > $parallel_degrees_limit; - next if abs($s1s2_angle) < $parallel_degrees_limit; # ignore parallel lines - next if $s1s2_angle > $max_angle; # ignore too sharp vertices - my @arc_points = ($points[$i], $points[$i+3]), # first and last points - - # now look for more points - my $last_line_angle = $s3_angle; - my $last_j = $i+3; - for (my $j = $i+3; $j < $#points; $j++) { - my $line = Slic3r::Line->new($points[$j], $points[$j+1]); - last if abs($line->length - $s1_len) > $len_epsilon; - my $line_angle = $line->atan; - $line_angle += 2*PI if $line_angle < 0; - my $anglediff = $line_angle - $last_line_angle; - last if abs($s1s2_angle - $anglediff) > $parallel_degrees_limit; - - # point $j+1 belongs to the arc - $arc_points[-1] = $points[$j+1]; - $last_j = $j+1; - - $last_line_angle = $line_angle; - } - - # s1, s2, s3 form an arc - my $orientation = $s1->point_on_left($points[$i+2]) ? 'ccw' : 'cw'; - - # to find the center, we intersect the perpendicular lines - # passing by midpoints of $s1 and last segment - # a better method would be to draw all the perpendicular lines - # and find the centroid of the enclosed polygon, or to - # intersect multiple lines and find the centroid of the convex hull - # around the intersections - my $arc_center; - { - my $s1_mid = $s1->midpoint; - my $last_mid = Slic3r::Line->new($points[$last_j-1], $points[$last_j])->midpoint; - my $rotation_angle = PI/2 * ($orientation eq 'ccw' ? -1 : 1); - my $ray1 = Slic3r::Line->new($s1_mid, rotate_points($rotation_angle, $s1_mid, $points[$i+1])); - my $last_ray = Slic3r::Line->new($last_mid, rotate_points($rotation_angle, $last_mid, $points[$last_j])); - $arc_center = $ray1->intersection($last_ray, 0) // next POINT; - } - - my $arc = Slic3r::ExtrusionPath::Arc->new( - polyline => Slic3r::Polyline->new(@arc_points), - role => $self->role, - flow_spacing => $self->flow_spacing, - orientation => $orientation, - center => $arc_center, - radius => $arc_center->distance_to($points[$i]), - ); - - # points 0..$i form a linear path - push @paths, $self->clone(polyline => Slic3r::Polyline->new(@points[0..$i])) - if $i > 0; - - # add our arc - push @paths, $arc; - Slic3r::debugf "ARC DETECTED\n"; - - # remove arc points from path, leaving one - splice @points, 0, $last_j, (); - - next CYCLE; - } - last; - } - - # remaining points form a linear path - push @paths, $self->clone(polyline => Slic3r::Polyline->new(@points)) - if @points > 1; - - return @paths; -} - 1; diff --git a/lib/Slic3r/ExtrusionPath/Arc.pm b/lib/Slic3r/ExtrusionPath/Arc.pm deleted file mode 100644 index 330d84543..000000000 --- a/lib/Slic3r/ExtrusionPath/Arc.pm +++ /dev/null @@ -1,34 +0,0 @@ -package Slic3r::ExtrusionPath::Arc; -use Moo; - -has 'polyline' => (is => 'rw', required => 1); -has 'role' => (is => 'rw', required => 1); -has 'height' => (is => 'rw'); -has 'flow_spacing' => (is => 'rw'); -has 'center' => (is => 'ro', required => 1); -has 'radius' => (is => 'ro', required => 1); -has 'orientation' => (is => 'ro', required => 1); # cw/ccw - -use Slic3r::Geometry qw(PI angle3points); - -sub points { - my $self = shift; - return $self->polyline; -} - -sub angle { - my $self = shift; - return angle3points($self->center, @{$self->points}); -} - -sub length { - my $self = shift; - - if($self->orientation eq 'ccw') { - return $self->radius * $self->angle; - } else { - return $self->radius * (2*PI() - $self->angle); - } -} - -1; diff --git a/lib/Slic3r/ExtrusionPath/Collection.pm b/lib/Slic3r/ExtrusionPath/Collection.pm index 86d7be1c8..e144941e6 100644 --- a/lib/Slic3r/ExtrusionPath/Collection.pm +++ b/lib/Slic3r/ExtrusionPath/Collection.pm @@ -47,10 +47,4 @@ sub cleanup { $self->append(@paths); } -sub detect_arcs { - my $self = shift; - - return map $_->detect_arcs(@_), @$self; -} - 1; diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 56ddd9ec6..715d436ad 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -273,15 +273,6 @@ sub extrude_path { $path->simplify(&Slic3r::SCALED_RESOLUTION); - # detect arcs - if ($self->config->gcode_arcs && !$params{dont_detect_arcs}) { - my $gcode = ""; - foreach my $arc_path ($path->detect_arcs) { - $gcode .= $self->extrude_path($arc_path, $description, %params, dont_detect_arcs => 1); - } - return $gcode; - } - # go to first point of extrusion path my $gcode = ""; my $first_point = $path->first_point; @@ -330,39 +321,7 @@ sub extrude_path { # extrude arc or line $gcode .= ";_BRIDGE_FAN_START\n" if $path->is_bridge; my $path_length = 0; - if ($path->isa('Slic3r::ExtrusionPath::Arc')) { - $path_length = unscale $path->length; - - # calculate extrusion length for this arc - my $E = 0; - if ($e) { - $E = $e * $path_length; - $self->extruder->e(0) if $self->config->use_relative_e_distances; - $self->total_extrusion_length($self->total_extrusion_length + $E); - $E = $self->extruder->e($self->extruder->e + $E); - } - - # compose G-code line - my $point = $path->points->[-1]; - $gcode .= $path->orientation eq 'cw' ? "G2" : "G3"; - $gcode .= sprintf " X%.3f Y%.3f", - ($point->x * &Slic3r::SCALING_FACTOR) + $self->shift_x - $self->extruder->extruder_offset->[X], - ($point->y * &Slic3r::SCALING_FACTOR) + $self->shift_y - $self->extruder->extruder_offset->[Y]; #** - - # XY distance of the center from the start position - $gcode .= sprintf " I%.3f J%.3f", - ($path->center->[X] - $self->last_pos->[X]) * &Slic3r::SCALING_FACTOR, - ($path->center->[Y] - $self->last_pos->[Y]) * &Slic3r::SCALING_FACTOR; - - $gcode .= sprintf(" %s%.5f", $self->config->extrusion_axis, $E) - if $E; - $gcode .= " F$F"; - $gcode .= " ; $description" - if $self->config->gcode_comments; - $gcode .= "\n"; - - $self->wipe_path(undef); - } else { + { my $local_F = $F; foreach my $line ($path->lines) { $path_length += my $line_length = unscale $line->length; diff --git a/lib/Slic3r/GCode/ArcFitting.pm b/lib/Slic3r/GCode/ArcFitting.pm new file mode 100644 index 000000000..019ebc859 --- /dev/null +++ b/lib/Slic3r/GCode/ArcFitting.pm @@ -0,0 +1,133 @@ +package Slic3r::GCode::ArcFitting; +use Moo; + +use Slic3r::Geometry qw(X Y PI scale unscale deg2rad); + +extends 'Slic3r::GCode::Reader'; +has 'config' => (is => 'ro', required => 1); +has 'max_angle' => (is => 'rw', default => sub { deg2rad(15) }); +has 'len_epsilon' => (is => 'rw', default => sub { scale 10 }); +has 'parallel_degrees_limit' => (is => 'rw', default => sub { abs(deg2rad(3)) }); + +sub process { + my $self = shift; + my ($gcode) = @_; + + my $new_gcode = ""; + my $buffer = ""; + my @cur_path = (); + my $cur_len = 0; + my $cur_relative_angle = 0; + + $self->parse($gcode, sub { + my ($reader, $cmd, $args, $info) = @_; + + if ($info->{extruding} && $info->{dist_XY} > 0) { + my $point = Slic3r::Point->new_scale($args->{X}, $args->{Y}); + + if (@cur_path >= 2) { + if ($cur_path[-1]->distance_to($point) > $self->len_epsilon) { + # if the last distance is not compatible with the current arc, flush it + $new_gcode .= $self->flush_path(\@cur_path, \$buffer); + } elsif (@cur_path >= 3) { + my $rel_angle = relative_angle(@cur_path[-2,-1], $point); + if (($cur_relative_angle != 0 && abs($rel_angle - $cur_relative_angle) > $self->parallel_degrees_limit) # relative angle is too different from the previous one + || abs($rel_angle) < $self->parallel_degrees_limit # relative angle is almost parallel + || $rel_angle > $self->max_angle) { # relative angle is excessive (too sharp) + # in these cases, $point does not really look like an additional point of the current arc + $new_gcode .= $self->flush_path(\@cur_path, \$buffer); + } + } + } + + if (@cur_path == 0) { + # we're starting a path, so let's prepend the previous position + push @cur_path, Slic3r::Point->new_scale($self->X, $self->Y), $point; + $buffer .= $info->{raw} . "\n"; + $cur_len = $cur_path[0]->distance_to($cur_path[1]); + } else { + push @cur_path, $point; + $buffer .= $info->{raw} . "\n"; + if (@cur_path == 3) { + # we have two segments, time to compute a reference angle + $cur_relative_angle = relative_angle(@cur_path[0,1,2]); + } + } + } else { + $new_gcode .= $self->flush_path(\@cur_path, \$buffer); + $new_gcode .= $info->{raw} . "\n"; + } + }); + + $new_gcode .= $self->flush_path(\@cur_path, \$buffer); + return $new_gcode; +} + +sub flush_path { + my ($self, $cur_path, $buffer) = @_; + + my $gcode = ""; + + if (@$cur_path >= 3) { + # if we have enough points, then we have an arc + $$buffer =~ s/^/;/mg; + $gcode = "; these moves were replaced by an arc:\n" . $$buffer; + + my $orientation = Slic3r::Geometry::point_is_on_left_of_segment($cur_path->[2], [ @$cur_path[0,1] ]) ? 'ccw' : 'cw'; + + # to find the center, we intersect the perpendicular lines + # passing by midpoints of $s1 and last segment + # a better method would be to draw all the perpendicular lines + # and find the centroid of the enclosed polygon, or to + # intersect multiple lines and find the centroid of the convex hull + # around the intersections + my $arc_center; + { + my $s1_mid = Slic3r::Line->new(@$cur_path[0,1])->midpoint; + my $last_mid = Slic3r::Line->new(@$cur_path[-2,-1])->midpoint; + my $rotation_angle = PI/2 * ($orientation eq 'ccw' ? -1 : 1); + my $ray1 = Slic3r::Line->new($s1_mid, $cur_path->[1]->clone->rotate($rotation_angle, $s1_mid)); + my $last_ray = Slic3r::Line->new($last_mid, $cur_path->[-1]->clone->rotate($rotation_angle, $last_mid)); + $arc_center = $ray1->intersection($last_ray, 0) or next POINT; + } + my $radius = $arc_center->distance_to($cur_path->[0]); + my $total_angle = Slic3r::Geometry::angle3points($arc_center, @$cur_path[0,-1]); + my $length = $orientation eq 'ccw' + ? $radius * $total_angle + : $radius * (2*PI - $total_angle); + + # compose G-code line + $gcode .= $orientation eq 'cw' ? "G2" : "G3"; + $gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$cur_path->[-1]}; # destination point + + # XY distance of the center from the start position + $gcode .= sprintf " I%.3f J%.3f", map { unscale($arc_center->[$_] - $cur_path->[0][$_]) } (X,Y); + + my $E = 0; # TODO: compute E using $length + $gcode .= sprintf(" %s%.5f", $self->config->extrusion_axis, $E) + if $E; + + my $F = 0; # TODO: extract F from original moves + $gcode .= " F$F\n"; + } else { + $gcode = $$buffer; + } + + $$buffer = ""; + splice @$cur_path, 0, $#$cur_path; # keep last point as starting position for next path + return $gcode; +} + +sub relative_angle { + my ($p1, $p2, $p3) = @_; + + my $s1 = Slic3r::Line->new($p1, $p2); + my $s2 = Slic3r::Line->new($p2, $p3); + my $s1_angle = $s1->atan; + my $s2_angle = $s2->atan; + $s1_angle += 2*PI if $s1_angle < 0; + $s2_angle += 2*PI if $s2_angle < 0; + return $s2_angle - $s1_angle; +} + +1; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 78bf5bb01..7e74899df 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -10,6 +10,7 @@ has 'shift' => (is => 'ro', required => 1); has 'spiralvase' => (is => 'lazy'); has 'vibration_limit' => (is => 'lazy'); +has 'arc_fitting' => (is => 'lazy'); has 'skirt_done' => (is => 'rw', default => sub { {} }); # print_z => 1 has 'brim_done' => (is => 'rw'); has 'second_layer_things_done' => (is => 'rw'); @@ -31,6 +32,14 @@ sub _build_vibration_limit { : undef; } +sub _build_arc_fitting { + my $self = shift; + + return $Slic3r::Config->gcode_arcs + ? Slic3r::GCode::ArcFitting->new(config => $self->gcodegen->config) + : undef; +} + sub process_layer { my $self = shift; my ($layer, $object_copies) = @_; @@ -175,6 +184,10 @@ sub process_layer { $gcode = $self->vibration_limit->process($gcode) if $Slic3r::Config->vibration_limit != 0; + # apply arc fitting if enabled + $gcode = $self->arc_fitting->process($gcode) + if $Slic3r::Config->gcode_arcs; + return $gcode; } diff --git a/t/arcs.t b/t/arcs.t index 7d15b63f8..92f83964b 100644 --- a/t/arcs.t +++ b/t/arcs.t @@ -2,6 +2,7 @@ use Test::More; use strict; use warnings; +plan skip_all => 'arcs are currently disabled'; plan tests => 13; BEGIN {