1
package Slic3r::GCode::ArcFitting;
4
use Slic3r::Geometry qw(X Y PI scale unscale epsilon scaled_epsilon deg2rad angle3points);
6
extends 'Slic3r::GCode::Reader';
7
has 'config' => (is => 'ro', required => 0);
8
has 'min_segments' => (is => 'rw', default => sub { 2 });
9
has 'min_total_angle' => (is => 'rw', default => sub { deg2rad(30) });
10
has 'max_relative_angle' => (is => 'rw', default => sub { deg2rad(15) });
11
has 'len_epsilon' => (is => 'rw', default => sub { scale 0.2 });
12
has 'angle_epsilon' => (is => 'rw', default => sub { abs(deg2rad(10)) });
13
has '_extrusion_axis' => (is => 'lazy');
14
has '_path' => (is => 'rw');
15
has '_cur_F' => (is => 'rw');
16
has '_cur_E' => (is => 'rw');
17
has '_cur_E0' => (is => 'rw');
18
has '_comment' => (is => 'rw');
20
sub _build__extrusion_axis {
22
return $self->config ? $self->config->get_extrusion_axis : 'E';
29
die "Arc fitting is not available (incomplete feature)\n";
30
die "Arc fitting doesn't support extrusion axis not being E\n" if $self->_extrusion_axis ne 'E';
34
$self->parse($gcode, sub {
35
my ($reader, $cmd, $args, $info) = @_;
37
if ($info->{extruding} && $info->{dist_XY} > 0) {
38
# this is an extrusion segment
41
my $line = Slic3r::Line->new(
42
Slic3r::Point->new_scale($self->X, $self->Y),
43
Slic3r::Point->new_scale($args->{X}, $args->{Y}),
47
my $F = $args->{F} // $reader->F;
49
# get extrusion per unscaled distance unit
50
my $e = $info->{dist_E} / unscale($line->length);
52
if ($self->_path && $F == $self->_cur_F && abs($e - $self->_cur_E) < epsilon) {
53
# if speed and extrusion per unit are the same as the previous segments,
54
# append this segment to path
55
$self->_path->append($line->b);
56
} elsif ($self->_path) {
57
# segment can't be appended to previous path, so we flush the previous one
59
$new_gcode .= $self->path_to_gcode;
64
# if this is the first segment of a path, start it from scratch
65
$self->_path(Slic3r::Polyline->new(@$line));
68
$self->_cur_E0($self->E);
69
$self->_comment($info->{comment});
72
# if we have a path, we flush it and go on
73
$new_gcode .= $self->path_to_gcode if $self->_path;
74
$new_gcode .= $info->{raw} . "\n";
79
$new_gcode .= $self->path_to_gcode if $self->_path;
86
my @chunks = $self->detect_arcs($self->_path);
89
my $E = $self->_cur_E0;
90
foreach my $chunk (@chunks) {
91
if ($chunk->isa('Slic3r::Polyline')) {
92
my @lines = @{$chunk->lines};
94
$gcode .= sprintf "G1 F%s\n", $self->_cur_F;
95
foreach my $line (@lines) {
96
$E += $self->_cur_E * unscale($line->length);
97
$gcode .= sprintf "G1 X%.3f Y%.3f %s%.5f",
98
(map unscale($_), @{$line->b}),
99
$self->_extrusion_axis, $E;
100
$gcode .= sprintf " ; %s", $self->_comment if $self->_comment;
103
} elsif ($chunk->isa('Slic3r::GCode::ArcFitting::Arc')) {
104
$gcode .= !$chunk->is_ccw ? "G2" : "G3";
105
$gcode .= sprintf " X%.3f Y%.3f", map unscale($_), @{$chunk->end}; # destination point
107
# XY distance of the center from the start position
108
$gcode .= sprintf " I%.3f", unscale($chunk->center->[X] - $chunk->start->[X]);
109
$gcode .= sprintf " J%.3f", unscale($chunk->center->[Y] - $chunk->start->[Y]);
111
$E += $self->_cur_E * unscale($chunk->length);
112
$gcode .= sprintf " %s%.5f", $self->_extrusion_axis, $E;
114
$gcode .= sprintf " F%s\n", $self->_cur_F;
121
my ($self, $path) = @_;
125
my $polyline = undef;
126
my $arc_start = undef;
129
for (my $i = 1; $i <= $#points; ++$i) {
132
# we need at least three points to check whether they form an arc
134
my $len = $points[$i-1]->distance_to($points[$i]);
135
my $rel_angle = PI - angle3points(@points[$i, $i-1, $i+1]);
136
if (abs($rel_angle) <= $self->max_relative_angle) {
137
for (my $j = $i+1; $j <= $#points; ++$j) {
138
# check whether @points[($i-1)..$j] form an arc
139
last if abs($points[$j-1]->distance_to($points[$j]) - $len) > $self->len_epsilon;
140
last if abs(PI - angle3points(@points[$j-1, $j-2, $j]) - $rel_angle) > $self->angle_epsilon;
147
if (defined $end && ($end - $i + 1) >= $self->min_segments) {
148
my $arc = polyline_to_arc(Slic3r::Polyline->new(@points[($i-1)..$end]));
150
if (1||$arc->angle >= $self->min_total_angle) {
153
# continue scanning after arc points
159
# if last chunk was a polyline, append to it
160
if (@chunks && $chunks[-1]->isa('Slic3r::Polyline')) {
161
$chunks[-1]->append($points[$i]);
163
push @chunks, Slic3r::Polyline->new(@points[($i-1)..$i]);
170
sub polyline_to_arc {
173
my @points = @$polyline;
175
my $is_ccw = $points[2]->ccw(@points[0,1]) > 0;
177
# to find the center, we intersect the perpendicular lines
178
# passing by first and last vertex;
179
# a better method would be to draw all the perpendicular lines
180
# and find the centroid of the enclosed polygon, or to
181
# intersect multiple lines and find the centroid of the convex hull
182
# around the intersections
185
my $first_ray = Slic3r::Line->new(@points[0,1]);
186
$first_ray->rotate(PI/2 * ($is_ccw ? 1 : -1), $points[0]);
188
my $last_ray = Slic3r::Line->new(@points[-2,-1]);
189
$last_ray->rotate(PI/2 * ($is_ccw ? -1 : 1), $points[-1]);
191
# require non-parallel rays in order to compute an accurate center
192
return if abs($first_ray->atan2_ - $last_ray->atan2_) < deg2rad(30);
194
$arc_center = $first_ray->intersection($last_ray, 0) or return;
197
# angle measured in ccw orientation
198
my $abs_angle = Slic3r::Geometry::angle3points($arc_center, @points[0,-1]);
200
my $rel_angle = $is_ccw
202
: (2*PI - $abs_angle);
204
my $arc = Slic3r::GCode::ArcFitting::Arc->new(
205
start => $points[0]->clone,
206
end => $points[-1]->clone,
207
center => $arc_center,
208
is_ccw => $is_ccw || 0,
213
printf "points = %d, path length = %f, arc angle = %f, arc length = %f\n",
215
unscale(Slic3r::Polyline->new(@points)->length),
216
Slic3r::Geometry::rad2deg($rel_angle),
217
unscale($arc->length);
223
package Slic3r::GCode::ArcFitting::Arc;
226
has 'start' => (is => 'ro', required => 1);
227
has 'end' => (is => 'ro', required => 1);
228
has 'center' => (is => 'ro', required => 1);
229
has 'is_ccw' => (is => 'ro', required => 1);
230
has 'angle' => (is => 'ro', required => 1);
234
return $self->start->distance_to($self->center);
239
return $self->radius * $self->angle;