2011-09-04 10:04:01 +00:00
|
|
|
package Slic3r::ExtrusionPath;
|
2011-09-06 09:50:43 +00:00
|
|
|
use Moo;
|
2011-09-04 10:04:01 +00:00
|
|
|
|
2012-05-19 13:40:11 +00:00
|
|
|
require Exporter;
|
|
|
|
our @ISA = qw(Exporter);
|
2012-12-05 09:47:41 +00:00
|
|
|
our @EXPORT_OK = qw(EXTR_ROLE_PERIMETER EXTR_ROLE_EXTERNAL_PERIMETER
|
2013-05-14 18:19:42 +00:00
|
|
|
EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER EXTR_ROLE_OVERHANG_PERIMETER
|
2013-03-16 23:42:53 +00:00
|
|
|
EXTR_ROLE_FILL EXTR_ROLE_SOLIDFILL EXTR_ROLE_TOPSOLIDFILL EXTR_ROLE_BRIDGE
|
|
|
|
EXTR_ROLE_INTERNALBRIDGE EXTR_ROLE_SKIRT EXTR_ROLE_SUPPORTMATERIAL EXTR_ROLE_GAPFILL);
|
2012-05-19 13:40:11 +00:00
|
|
|
our %EXPORT_TAGS = (roles => \@EXPORT_OK);
|
|
|
|
|
2011-12-30 18:59:51 +00:00
|
|
|
use Slic3r::Geometry qw(PI X Y epsilon deg2rad rotate_points);
|
|
|
|
|
|
|
|
# the underlying Slic3r::Polyline objects holds the geometry
|
|
|
|
has 'polyline' => (
|
2012-05-19 15:57:38 +00:00
|
|
|
is => 'rw',
|
2011-12-30 18:59:51 +00:00
|
|
|
required => 1,
|
2013-03-18 17:03:14 +00:00
|
|
|
handles => [qw(merge_continuous_lines lines length reverse clip_end)],
|
2011-12-30 18:59:51 +00:00
|
|
|
);
|
2011-09-04 10:04:01 +00:00
|
|
|
|
2012-10-28 13:22:51 +00:00
|
|
|
# height is the vertical thickness of the extrusion expressed in mm
|
2012-10-28 15:59:20 +00:00
|
|
|
has 'height' => (is => 'rw');
|
2013-05-16 10:54:38 +00:00
|
|
|
has 'flow_spacing' => (is => 'rw', required => 1);
|
2011-12-05 11:15:52 +00:00
|
|
|
has 'role' => (is => 'rw', required => 1);
|
2012-07-20 12:39:07 +00:00
|
|
|
|
2012-05-19 20:36:29 +00:00
|
|
|
use constant EXTR_ROLE_PERIMETER => 0;
|
2013-05-14 18:19:42 +00:00
|
|
|
use constant EXTR_ROLE_EXTERNAL_PERIMETER => 1;
|
|
|
|
use constant EXTR_ROLE_OVERHANG_PERIMETER => 2;
|
2012-06-27 17:37:34 +00:00
|
|
|
use constant EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER => 3;
|
|
|
|
use constant EXTR_ROLE_FILL => 4;
|
|
|
|
use constant EXTR_ROLE_SOLIDFILL => 5;
|
|
|
|
use constant EXTR_ROLE_TOPSOLIDFILL => 6;
|
|
|
|
use constant EXTR_ROLE_BRIDGE => 7;
|
2013-03-16 23:42:53 +00:00
|
|
|
use constant EXTR_ROLE_INTERNALBRIDGE => 8;
|
|
|
|
use constant EXTR_ROLE_SKIRT => 9;
|
|
|
|
use constant EXTR_ROLE_SUPPORTMATERIAL => 10;
|
|
|
|
use constant EXTR_ROLE_GAPFILL => 11;
|
2011-11-28 17:37:53 +00:00
|
|
|
|
2012-10-28 13:22:51 +00:00
|
|
|
use constant PACK_FMT => 'ffca*';
|
2012-05-19 15:57:38 +00:00
|
|
|
|
2012-07-20 12:39:07 +00:00
|
|
|
# class or object method
|
|
|
|
sub pack {
|
2012-05-19 15:57:38 +00:00
|
|
|
my $self = shift;
|
2012-07-20 12:39:07 +00:00
|
|
|
my %args = @_;
|
|
|
|
|
|
|
|
if (ref $self) {
|
2012-10-28 13:22:51 +00:00
|
|
|
%args = map { $_ => $self->$_ } qw(height flow_spacing role polyline);
|
2012-07-20 12:39:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
my $o = \ pack PACK_FMT,
|
2012-10-28 13:22:51 +00:00
|
|
|
$args{height} // -1,
|
2012-07-20 12:39:07 +00:00
|
|
|
$args{flow_spacing} || -1,
|
|
|
|
$args{role} // (die "Missing mandatory attribute 'role'"), #/
|
|
|
|
$args{polyline}->serialize;
|
|
|
|
|
|
|
|
bless $o, 'Slic3r::ExtrusionPath::Packed';
|
|
|
|
return $o;
|
2011-12-30 18:59:51 +00:00
|
|
|
}
|
2011-10-01 12:26:54 +00:00
|
|
|
|
2012-07-20 12:39:07 +00:00
|
|
|
# no-op, this allows to use both packed and non-packed objects in Collections
|
|
|
|
sub unpack { $_[0] }
|
|
|
|
|
2013-06-20 18:11:46 +00:00
|
|
|
sub clone {
|
|
|
|
my $self = shift;
|
|
|
|
my %p = @_;
|
|
|
|
|
|
|
|
$p{polyline} ||= $self->polyline->clone;
|
|
|
|
return (ref $self)->new(
|
|
|
|
(map { $_ => $self->$_ } qw(polyline height flow_spacing role)),
|
|
|
|
%p,
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2012-02-25 13:46:21 +00:00
|
|
|
sub clip_with_polygon {
|
|
|
|
my $self = shift;
|
|
|
|
my ($polygon) = @_;
|
|
|
|
|
|
|
|
return $self->clip_with_expolygon(Slic3r::ExPolygon->new($polygon));
|
|
|
|
}
|
|
|
|
|
2012-02-19 11:03:36 +00:00
|
|
|
sub clip_with_expolygon {
|
|
|
|
my $self = shift;
|
|
|
|
my ($expolygon) = @_;
|
|
|
|
|
2013-06-20 18:11:46 +00:00
|
|
|
return map $self->clone(polyline => $_),
|
|
|
|
$self->polyline->clip_with_expolygon($expolygon);
|
|
|
|
}
|
|
|
|
|
|
|
|
sub intersect_expolygons {
|
|
|
|
my $self = shift;
|
|
|
|
my ($expolygons) = @_;
|
|
|
|
|
|
|
|
return map $self->clone(polyline => Slic3r::Polyline->new(@$_)),
|
|
|
|
@{Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection($expolygons, [$self->polyline])};
|
|
|
|
}
|
|
|
|
|
|
|
|
sub subtract_expolygons {
|
|
|
|
my $self = shift;
|
|
|
|
my ($expolygons) = @_;
|
|
|
|
|
|
|
|
return map $self->clone(polyline => Slic3r::Polyline->new(@$_)),
|
|
|
|
@{Boost::Geometry::Utils::multi_linestring_multi_polygon_difference([$self->polyline], $expolygons)};
|
2012-02-19 11:03:36 +00:00
|
|
|
}
|
|
|
|
|
2013-03-16 17:42:56 +00:00
|
|
|
sub simplify {
|
|
|
|
my $self = shift;
|
|
|
|
$self->polyline($self->polyline->simplify(@_));
|
|
|
|
}
|
|
|
|
|
2011-12-30 18:59:51 +00:00
|
|
|
sub points {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->polyline;
|
|
|
|
}
|
|
|
|
|
2013-03-10 15:09:03 +00:00
|
|
|
sub first_point {
|
2011-09-26 08:52:58 +00:00
|
|
|
my $self = shift;
|
2013-03-10 15:09:03 +00:00
|
|
|
return $self->polyline->[0];
|
2011-09-26 08:52:58 +00:00
|
|
|
}
|
|
|
|
|
2013-03-09 19:28:03 +00:00
|
|
|
sub is_perimeter {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->role == EXTR_ROLE_PERIMETER
|
|
|
|
|| $self->role == EXTR_ROLE_EXTERNAL_PERIMETER
|
2013-05-14 18:19:42 +00:00
|
|
|
|| $self->role == EXTR_ROLE_OVERHANG_PERIMETER
|
2013-03-09 19:28:03 +00:00
|
|
|
|| $self->role == EXTR_ROLE_CONTOUR_INTERNAL_PERIMETER;
|
|
|
|
}
|
|
|
|
|
2013-03-04 22:37:58 +00:00
|
|
|
sub is_fill {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->role == EXTR_ROLE_FILL
|
|
|
|
|| $self->role == EXTR_ROLE_SOLIDFILL
|
|
|
|
|| $self->role == EXTR_ROLE_TOPSOLIDFILL;
|
|
|
|
}
|
|
|
|
|
2013-05-10 18:53:49 +00:00
|
|
|
sub is_bridge {
|
|
|
|
my $self = shift;
|
|
|
|
return $self->role == EXTR_ROLE_BRIDGE
|
2013-05-14 18:19:42 +00:00
|
|
|
|| $self->role == EXTR_ROLE_INTERNALBRIDGE
|
|
|
|
|| $self->role == EXTR_ROLE_OVERHANG_PERIMETER;
|
2013-05-10 18:53:49 +00:00
|
|
|
}
|
|
|
|
|
2011-10-01 12:26:54 +00:00
|
|
|
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
|
2013-06-20 18:11:46 +00:00
|
|
|
push @paths, $self->clone(polyline => Slic3r::Polyline->new(\@p));
|
2011-10-01 12:26:54 +00:00
|
|
|
@p = ($p3);
|
|
|
|
push @p, grep $_, shift @points or last;
|
|
|
|
} else {
|
|
|
|
push @p, $p3;
|
|
|
|
}
|
|
|
|
}
|
2013-06-20 18:11:46 +00:00
|
|
|
push @paths, $self->clone(polyline => Slic3r::Polyline->new(\@p))
|
|
|
|
if @p > 1;
|
2011-11-28 17:37:53 +00:00
|
|
|
|
2011-10-01 12:26:54 +00:00
|
|
|
return @paths;
|
|
|
|
}
|
|
|
|
|
2011-10-20 16:11:59 +00:00
|
|
|
sub detect_arcs {
|
|
|
|
my $self = shift;
|
2011-11-07 13:12:07 +00:00
|
|
|
my ($max_angle, $len_epsilon) = @_;
|
2011-10-20 16:11:59 +00:00
|
|
|
|
2011-11-07 13:12:07 +00:00
|
|
|
$max_angle = deg2rad($max_angle || 15);
|
2012-07-27 19:13:03 +00:00
|
|
|
$len_epsilon ||= 10 / &Slic3r::SCALING_FACTOR;
|
2012-10-22 12:08:27 +00:00
|
|
|
my $parallel_degrees_limit = abs(Slic3r::Geometry::deg2rad(3));
|
2011-10-20 16:11:59 +00:00
|
|
|
|
|
|
|
my @points = @{$self->points};
|
|
|
|
my @paths = ();
|
|
|
|
|
|
|
|
# we require at least 3 consecutive segments to form an arc
|
|
|
|
CYCLE: while (@points >= 4) {
|
2011-12-25 12:01:48 +00:00
|
|
|
POINT: for (my $i = 0; $i <= $#points - 3; $i++) {
|
2011-10-20 16:11:59 +00:00
|
|
|
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;
|
2012-09-12 13:58:01 +00:00
|
|
|
$s2_angle += 2*PI if $s2_angle < 0;
|
|
|
|
$s3_angle += 2*PI if $s3_angle < 0;
|
2011-10-20 16:11:59 +00:00
|
|
|
my $s1s2_angle = $s2_angle - $s1_angle;
|
|
|
|
my $s2s3_angle = $s3_angle - $s2_angle;
|
2012-10-22 12:08:27 +00:00
|
|
|
next if abs($s1s2_angle - $s2s3_angle) > $parallel_degrees_limit;
|
|
|
|
next if abs($s1s2_angle) < $parallel_degrees_limit; # ignore parallel lines
|
2011-10-20 16:11:59 +00:00
|
|
|
next if $s1s2_angle > $max_angle; # ignore too sharp vertices
|
2011-11-07 13:12:07 +00:00
|
|
|
my @arc_points = ($points[$i], $points[$i+3]), # first and last points
|
2011-10-20 16:11:59 +00:00
|
|
|
|
|
|
|
# now look for more points
|
|
|
|
my $last_line_angle = $s3_angle;
|
2011-11-07 13:12:07 +00:00
|
|
|
my $last_j = $i+3;
|
2011-10-20 16:11:59 +00:00
|
|
|
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;
|
2012-09-12 13:58:01 +00:00
|
|
|
$line_angle += 2*PI if $line_angle < 0;
|
2011-10-20 16:11:59 +00:00
|
|
|
my $anglediff = $line_angle - $last_line_angle;
|
2012-10-22 12:08:27 +00:00
|
|
|
last if abs($s1s2_angle - $anglediff) > $parallel_degrees_limit;
|
2012-09-12 13:58:01 +00:00
|
|
|
|
2011-10-20 16:11:59 +00:00
|
|
|
# point $j+1 belongs to the arc
|
2011-11-07 13:12:07 +00:00
|
|
|
$arc_points[-1] = $points[$j+1];
|
2011-10-20 16:11:59 +00:00
|
|
|
$last_j = $j+1;
|
|
|
|
|
|
|
|
$last_line_angle = $line_angle;
|
|
|
|
}
|
|
|
|
|
2011-11-07 13:12:07 +00:00
|
|
|
# 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]));
|
2011-12-25 12:01:48 +00:00
|
|
|
$arc_center = $ray1->intersection($last_ray, 0) or next POINT;
|
2011-11-07 13:12:07 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
my $arc = Slic3r::ExtrusionPath::Arc->new(
|
2011-12-30 18:59:51 +00:00
|
|
|
polyline => Slic3r::Polyline->new(\@arc_points),
|
2011-11-28 17:37:53 +00:00
|
|
|
role => $self->role,
|
2013-05-16 10:54:38 +00:00
|
|
|
flow_spacing => $self->flow_spacing,
|
2011-11-07 13:12:07 +00:00
|
|
|
orientation => $orientation,
|
|
|
|
center => $arc_center,
|
|
|
|
radius => $arc_center->distance_to($points[$i]),
|
|
|
|
);
|
|
|
|
|
2011-10-20 16:11:59 +00:00
|
|
|
# points 0..$i form a linear path
|
2013-06-20 18:11:46 +00:00
|
|
|
push @paths, $self->clone(polyline => Slic3r::Polyline->new(@points[0..$i]))
|
|
|
|
if $i > 0;
|
2011-10-20 16:11:59 +00:00
|
|
|
|
|
|
|
# add our arc
|
|
|
|
push @paths, $arc;
|
2011-11-07 13:12:07 +00:00
|
|
|
Slic3r::debugf "ARC DETECTED\n";
|
|
|
|
|
2011-10-20 16:11:59 +00:00
|
|
|
# remove arc points from path, leaving one
|
|
|
|
splice @points, 0, $last_j, ();
|
|
|
|
|
|
|
|
next CYCLE;
|
|
|
|
}
|
|
|
|
last;
|
|
|
|
}
|
|
|
|
|
|
|
|
# remaining points form a linear path
|
2013-06-20 18:11:46 +00:00
|
|
|
push @paths, $self->clone(polyline => Slic3r::Polyline->new(\@points))
|
|
|
|
if @points > 1;
|
2011-10-20 16:11:59 +00:00
|
|
|
|
|
|
|
return @paths;
|
|
|
|
}
|
|
|
|
|
2012-07-20 12:39:07 +00:00
|
|
|
package Slic3r::ExtrusionPath::Packed;
|
|
|
|
sub unpack {
|
|
|
|
my $self = shift;
|
|
|
|
|
2012-10-28 13:22:51 +00:00
|
|
|
my ($height, $flow_spacing, $role, $polyline_s)
|
2012-07-20 12:39:07 +00:00
|
|
|
= unpack Slic3r::ExtrusionPath::PACK_FMT, $$self;
|
|
|
|
|
|
|
|
return Slic3r::ExtrusionPath->new(
|
2012-10-28 13:22:51 +00:00
|
|
|
height => ($height == -1) ? undef : $height,
|
2012-07-20 12:39:07 +00:00
|
|
|
flow_spacing => ($flow_spacing == -1) ? undef : $flow_spacing,
|
|
|
|
role => $role,
|
|
|
|
polyline => Slic3r::Polyline->deserialize($polyline_s),
|
|
|
|
);
|
|
|
|
}
|
|
|
|
|
2011-09-04 10:04:01 +00:00
|
|
|
1;
|