1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
|
package Slic3r::ExtrusionPath;
use Moo;
extends 'Slic3r::Polyline';
# this integer represents the vertical thickness of the extrusion
# expressed in layers
has 'depth_layers' => (is => 'ro', default => sub {1});
# multiplier for the flow rate
has 'flow_ratio' => (is => 'rw');
# perimeter/fill/solid-fill/bridge/skirt
has 'role' => (is => 'rw', required => 1);
use Slic3r::Geometry qw(PI X Y epsilon deg2rad rotate_points);
use XXX;
sub clip_end {
my $self = shift;
my ($distance) = @_;
while ($distance > 0) {
my $last_point = pop @{$self->points};
last if !@{$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, $self->points->[-1], $distance);
push @{$self->points}, Slic3r::Point->new($new_point);
$distance = 0;
}
}
sub endpoints {
my $self = shift;
return ($self->points->[0], $self->points->[-1]);
}
sub reverse {
my $self = shift;
@{$self->points} = reverse @{$self->points};
}
sub split_at_acute_angles {
my $self = shift;
# calculate angle limit
my $angle_limit = abs(Slic3r::Geometry::deg2rad(40));
my @points = @{$self->p};
my @paths = ();
# take first two points
my @p = splice @points, 0, 2;
# loop until we have one spare point
while (my $p3 = shift @points) {
my $angle = abs(Slic3r::Geometry::angle3points($p[-1], $p[-2], $p3));
$angle = 2*PI - $angle if $angle > PI;
if ($angle < $angle_limit) {
# if the angle between $p[-2], $p[-1], $p3 is too acute
# then consider $p3 only as a starting point of a new
# path and stop the current one as it is
push @paths, (ref $self)->cast(
[@p],
role => $self->role,
depth_layers => $self->depth_layers,
);
@p = ($p3);
push @p, grep $_, shift @points or last;
} else {
push @p, $p3;
}
}
push @paths, (ref $self)->cast(
[@p],
role => $self->role,
depth_layers => $self->depth_layers,
) if @p > 1;
return @paths;
}
sub detect_arcs {
my $self = shift;
my ($max_angle, $len_epsilon) = @_;
$max_angle = deg2rad($max_angle || 15);
$len_epsilon ||= 10 / $Slic3r::resolution;
my @points = @{$self->points};
my @paths = ();
# we require at least 3 consecutive segments to form an arc
CYCLE: while (@points >= 4) {
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) > $Slic3r::Geometry::parallel_degrees_limit;
next if abs($s1s2_angle) < $Slic3r::Geometry::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) > $Slic3r::Geometry::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);
}
my $arc = Slic3r::ExtrusionPath::Arc->new(
points => [@arc_points],
role => $self->role,
orientation => $orientation,
center => $arc_center,
radius => $arc_center->distance_to($points[$i]),
);
# points 0..$i form a linear path
push @paths, (ref $self)->new(
points => [ @points[0..$i] ],
role => $self->role,
depth_layers => $self->depth_layers,
) 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, (ref $self)->new(
points => [@points],
role => $self->role,
depth_layers => $self->depth_layers,
) if @points > 1;
return @paths;
}
1;
|