Merge branch 'avoid-crossing-perimeters'

Conflicts:
	lib/Slic3r/GCode.pm
medial-thinwall
Alessandro Ranellucci 2013-01-28 10:43:58 +01:00
commit a016a06fa6
18 changed files with 404 additions and 26 deletions

View File

@ -7,7 +7,7 @@ my $build = Module::Build->new(
dist_version => '0.1',
license => 'perl',
requires => {
'Boost::Geometry::Utils' => '0',
'Boost::Geometry::Utils' => '0.06',
'Encode::Locale' => '0',
'File::Basename' => '0',
'File::Spec' => '0',

View File

@ -24,6 +24,7 @@ lib/Slic3r/Format/AMF/Parser.pm
lib/Slic3r/Format/OBJ.pm
lib/Slic3r/Format/STL.pm
lib/Slic3r/GCode.pm
lib/Slic3r/GCode/MotionPlanner.pm
lib/Slic3r/Geometry.pm
lib/Slic3r/Geometry/Clipper.pm
lib/Slic3r/GUI.pm

View File

@ -190,6 +190,7 @@ The author of the Silk icon set is Mark James.
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)

View File

@ -29,6 +29,7 @@ our $var = "$FindBin::Bin/var";
use Encode;
use Encode::Locale;
use Boost::Geometry::Utils 0.06;
use Moo 0.091009;
use Slic3r::Config;
@ -44,6 +45,7 @@ use Slic3r::Format::AMF;
use Slic3r::Format::OBJ;
use Slic3r::Format::STL;
use Slic3r::GCode;
use Slic3r::GCode::MotionPlanner;
use Slic3r::Geometry qw(PI);
use Slic3r::Layer;
use Slic3r::Layer::Region;

View File

@ -524,6 +524,13 @@ our $Options = {
type => 'bool',
default => 1,
},
'avoid_crossing_perimeters' => {
label => 'Avoid crossing perimeters',
tooltip => 'Optimize travel moves in order to minimize the crossing of perimeters. This is mostly useful with Bowden extruders which suffer from oozing. This feature slows down both the print and the G-code generation.',
cli => 'avoid-crossing-perimeters!',
type => 'bool',
default => 0,
},
'only_retract_when_crossing_perimeters' => {
label => 'Only retract when crossing perimeters',
tooltip => 'Disables retraction when travelling between infill paths inside the same island.',

View File

@ -6,7 +6,7 @@ use warnings;
use Boost::Geometry::Utils;
use Math::Geometry::Voronoi;
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length);
use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length epsilon);
use Slic3r::Geometry::Clipper qw(union_ex JT_MITER);
# the constructor accepts an array of polygons
@ -159,15 +159,13 @@ sub clip_line {
my $self = shift;
my ($line) = @_; # line must be a Slic3r::Line object
return Boost::Geometry::Utils::polygon_linestring_intersection(
$self->boost_polygon,
$line->boost_linestring,
);
return Boost::Geometry::Utils::polygon_multi_linestring_intersection($self, [$line]);
}
sub simplify {
my $self = shift;
$_->simplify(@_) for @$self;
$self;
}
sub scale {
@ -178,11 +176,13 @@ sub scale {
sub translate {
my $self = shift;
$_->translate(@_) for @$self;
$self;
}
sub rotate {
my $self = shift;
$_->rotate(@_) for @$self;
$self;
}
sub area {

View File

@ -48,9 +48,9 @@ sub fill_surface {
# clip paths against a slightly offsetted expolygon, so that the first and last paths
# are kept even if the expolygon has vertical sides
my @paths = @{ Boost::Geometry::Utils::polygon_linestring_intersection(
+($expolygon->offset_ex(scaled_epsilon))[0]->boost_polygon, # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
Boost::Geometry::Utils::linestring(@vertical_lines),
my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection(
+($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object
[ @vertical_lines ],
) };
for (@paths) {
$_->[0][Y] += $overlap_distance;

View File

@ -4,6 +4,7 @@ use Moo;
use List::Util qw(max first);
use Slic3r::ExtrusionPath ':roles';
use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B);
use Slic3r::Geometry::Clipper qw(union_ex);
has 'multiple_extruders' => (is => 'ro', default => sub {0} );
has 'layer_count' => (is => 'ro', required => 1 );
@ -14,6 +15,10 @@ has 'shift_y' => (is => 'rw', default => sub {0} );
has 'z' => (is => 'rw');
has 'speed' => (is => 'rw');
has 'external_mp' => (is => 'rw');
has 'layer_mp' => (is => 'rw');
has 'new_object' => (is => 'rw', default => sub {0});
has 'straight_once' => (is => 'rw', default => sub {1});
has 'extruder' => (is => 'rw');
has 'extrusion_distance' => (is => 'rw', default => sub {0} );
has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds
@ -60,7 +65,7 @@ sub set_shift {
# if shift increases (goes towards right), last_pos decreases because it goes towards left
$self->last_pos->translate(
scale ($self->shift_x - $shift[X]),
scale ($self->shift_x - $shift[Y]),
scale ($self->shift_y - $shift[Y]),
);
$self->shift_x($shift[X]);
@ -72,6 +77,11 @@ sub change_layer {
my ($layer) = @_;
$self->layer($layer);
if ($Slic3r::Config->avoid_crossing_perimeters) {
$self->layer_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @{$layer->slices} ], undef, 1),
));
}
my $gcode = "";
if ($Slic3r::Config->gcode_flavor =~ /^(?:makerbot|sailfish)$/) {
@ -154,8 +164,7 @@ sub extrude_loop {
$point->rotate($angle, $extrusion_path->polyline->[0]);
# generate the travel move
$self->speed('travel');
$gcode .= $self->G0($point, undef, 0, "move inwards before travel");
$gcode .= $self->travel_to($point, "move inwards before travel");
}
return $gcode;
@ -196,9 +205,7 @@ sub extrude_path {
}
# go to first point of extrusion path
$self->speed('travel');
$gcode .= $self->G0($path->points->[0], undef, 0, "move to first $description point")
if !points_coincide($self->last_pos, $path->points->[0]);
$gcode .= $self->travel_to($path->points->[0], "move to first $description point");
# compensate retraction
$gcode .= $self->unretract;
@ -251,6 +258,44 @@ sub extrude_path {
return $gcode;
}
sub travel_to {
my $self = shift;
my ($point, $comment) = @_;
return "" if points_coincide($self->last_pos, $point);
$self->speed('travel');
my $gcode = "";
if ($Slic3r::Config->avoid_crossing_perimeters && $self->last_pos->distance_to($point) > scale 5 && !$self->straight_once) {
my $plan = sub {
my $mp = shift;
return join '',
map $self->G0($_->[B], undef, 0, $comment || ""),
$mp->shortest_path($self->last_pos, $point)->lines;
};
if ($self->new_object) {
$self->new_object(0);
# represent $point in G-code coordinates
$point = $point->clone;
my @shift = ($self->shift_x, $self->shift_y);
$point->translate(map scale $_, @shift);
# calculate path (external_mp uses G-code coordinates so we temporary need a null shift)
$self->set_shift(0,0);
$gcode .= $plan->($self->external_mp);
$self->set_shift(@shift);
} else {
$gcode .= $plan->($self->layer_mp);
}
} else {
$self->straight_once(0);
$gcode .= $self->G0($point, undef, 0, $comment || "");
}
return $gcode;
}
sub retract {
my $self = shift;
my %params = @_;

View File

@ -0,0 +1,282 @@
package Slic3r::GCode::MotionPlanner;
use Moo;
has 'islands' => (is => 'ro', required => 1);
has 'no_internal' => (is => 'ro');
has 'last_crossings'=> (is => 'rw');
has '_inner' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
has '_outer' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of polygons
has '_contours_ex' => (is => 'rw', default => sub { [] }); # arrayref of arrayrefs of expolygons
has '_pointmap' => (is => 'rw', default => sub { {} }); # { id => $point }
has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { node_idx => distance, ... }
has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool
use List::Util qw(first);
use Slic3r::Geometry qw(A B scale epsilon nearest_point);
use Slic3r::Geometry::Clipper qw(diff_ex JT_MITER);
# clearance (in mm) from the perimeters
has '_inner_margin' => (is => 'ro', default => sub { scale 0.5 });
has '_outer_margin' => (is => 'ro', default => sub { scale 2 });
# this factor weigths the crossing of a perimeter
# vs. the alternative path. a value of 5 means that
# a perimeter will be crossed if the alternative path
# is >= 5x the length of the straight line we could
# follow if we decided to cross the perimeter.
# a nearly-infinite value for this will only permit
# perimeter crossing when there's no alternative path.
use constant CROSSING_FACTOR => 20;
use constant INFINITY => 'inf';
# setup our configuration space
sub BUILD {
my $self = shift;
my $edges = $self->_edges;
my $crossing_edges = $self->_crossing_edges;
my $tolerance = scale epsilon;
# given an expolygon, this subroutine connects all its visible points
my $add_expolygon = sub {
my ($expolygon, $crosses_perimeter) = @_;
my @points = map @$_, @$expolygon;
for my $i (0 .. $#points) {
for my $j (($i+1) .. $#points) {
my $line = Slic3r::Line->new($points[$i], $points[$j]);
if ($expolygon->encloses_line($line, $tolerance)) {
my $dist = $line->length * ($crosses_perimeter ? CROSSING_FACTOR : 1);
$edges->{$points[$i]}{$points[$j]} = $dist;
$edges->{$points[$j]}{$points[$i]} = $dist;
$crossing_edges->{$points[$i]}{$points[$j]} = 1;
$crossing_edges->{$points[$j]}{$points[$i]} = 1;
}
}
}
};
# process individual islands
for my $i (0 .. $#{$self->islands}) {
# simplify the island's contours
$self->islands->[$i]->simplify($self->_inner_margin);
# offset the island inwards to make the boundaries for internal movements
# so that no motion along external perimeters happens
$self->_inner->[$i] = [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ]
if !$self->no_internal;
# offset the island outwards to make the boundaries for external movements
$self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ];
# further simplification (isn't this a duplication of the one above?)
$_->simplify($self->_inner_margin) for @{$self->_inner->[$i]}, @{$self->_outer->[$i]};
# if internal motion is enabled, build a set of utility expolygons representing
# the outer boundaries (as contours) and the inner boundaries (as holes). whenever
# we jump from a hole to a contour or viceversa, we know we're crossing a perimeter
if (!$self->no_internal) {
$self->_contours_ex->[$i] = diff_ex(
$self->_outer->[$i],
[ map $_->contour, @{$self->_inner->[$i]} ],
);
# lines enclosed in inner expolygons are visible
$add_expolygon->($_) for @{ $self->_inner->[$i] };
# lines enclosed in expolygons covering perimeters are visible
# (but discouraged)
$add_expolygon->($_, 1) for @{ $self->_contours_ex->[$i] };
}
}
my $intersects = sub {
my ($polygon, $line) = @_;
@{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0;
};
{
my @outer = (map @$_, @{$self->_outer});
# lines of outer polygons connect visible points
for my $i (0 .. $#outer) {
foreach my $line ($outer[$i]->lines) {
my $dist = $line->length;
$edges->{$line->[A]}{$line->[B]} = $dist;
$edges->{$line->[B]}{$line->[A]} = $dist;
}
}
# lines connecting outer polygons are visible
for my $i (0 .. $#outer) {
for my $j (($i+1) .. $#outer) {
for my $m (0 .. $#{$outer[$i]}) {
for my $n (0 .. $#{$outer[$j]}) {
my $line = Slic3r::Line->new($outer[$i][$m], $outer[$j][$n]);
if (!first { $intersects->($_, $line) } @outer) {
# this line does not cross any polygon
my $dist = $line->length;
$edges->{$outer[$i][$m]}{$outer[$j][$n]} = $dist;
$edges->{$outer[$j][$n]}{$outer[$i][$m]} = $dist;
}
}
}
}
}
}
# lines connecting inner polygons contours are visible but discouraged
if (!$self->no_internal) {
my @inner = (map $_->contour, map @$_, @{$self->_inner});
for my $i (0 .. $#inner) {
for my $j (($i+1) .. $#inner) {
for my $m (0 .. $#{$inner[$i]}) {
for my $n (0 .. $#{$inner[$j]}) {
my $line = Slic3r::Line->new($inner[$i][$m], $inner[$j][$n]);
if (!first { $intersects->($_, $line) } @inner) {
# this line does not cross any polygon
my $dist = $line->length * CROSSING_FACTOR;
$edges->{$inner[$i][$m]}{$inner[$j][$n]} = $dist;
$edges->{$inner[$j][$n]}{$inner[$i][$m]} = $dist;
$crossing_edges->{$inner[$i][$m]}{$inner[$j][$n]} = 1;
$crossing_edges->{$inner[$j][$n]}{$inner[$i][$m]} = 1;
}
}
}
}
}
}
$self->_pointmap({
map +("$_" => $_),
(map @$_, map @$_, map @$_, @{$self->_inner}),
(map @$_, map @$_, @{$self->_outer}),
(map @$_, map @$_, map @$_, @{$self->_contours_ex}),
});
if (0) {
my @lines = ();
my %lines = ();
for my $i (keys %{$self->_edges}) {
for my $j (keys %{$self->_edges->{$i}}) {
next if $lines{join '_', sort $i, $j};
push @lines, [ map $self->_pointmap->{$_}, $i, $j ];
$lines{join '_', sort $i, $j} = 1;
}
}
require "Slic3r/SVG.pm";
Slic3r::SVG::output("space.svg",
lines => \@lines,
points => [ values %{$self->_pointmap} ],
no_arrows => 1,
polygons => [ map @$_, @{$self->islands} ],
#red_polygons => [ map $_->holes, map @$_, @{$self->_inner} ],
#white_polygons => [ map @$_, @{$self->_outer} ],
);
printf "%d islands\n", scalar @{$self->islands};
}
}
sub find_node {
my $self = shift;
my ($point, $near_to) = @_;
# for optimal pathing, we should check visibility from $point to all $candidates, and then
# choose the one that is nearest to $near_to among the visible ones; however this is probably too slow
# if we're inside a hole, move to a point on hole;
{
my $polygon = first { $_->encloses_point($point) } (map $_->holes, map @$_, @{$self->_inner});
return nearest_point($point, $polygon) if $polygon;
}
# if we're inside an expolygon move to a point on contour or holes
{
my $expolygon = first { $_->encloses_point_quick($point) } (map @$_, @{$self->_inner});
return nearest_point($point, [ map @$_, @$expolygon ]) if $expolygon;
}
{
my $outer_polygon_idx;
if (!$self->no_internal) {
# look for an outer expolygon whose contour contains our point
$outer_polygon_idx = first { first { $_->contour->encloses_point($point) } @{$self->_contours_ex->[$_]} }
0 .. $#{ $self->_contours_ex };
} else {
# # look for an outer expolygon containing our point
$outer_polygon_idx = first { first { $_->encloses_point($point) } @{$self->_outer->[$_]} }
0 .. $#{ $self->_outer };
}
my $candidates = defined $outer_polygon_idx
? [ map @{$_->contour}, @{$self->_inner->[$outer_polygon_idx]} ]
: [ map @$_, map @$_, @{$self->_outer} ];
$candidates = [ map @$_, @{$self->_outer->[$outer_polygon_idx]} ]
if @$candidates == 0;
return nearest_point($point, $candidates);
}
}
sub shortest_path {
my $self = shift;
my ($from, $to) = @_;
return Slic3r::Polyline->new($from, $to) if !@{$self->islands};
# find nearest nodes
my $new_from = $self->find_node($from, $to);
my $new_to = $self->find_node($to, $from);
my $root = "$new_from";
my $target = "$new_to";
my $edges = $self->_edges;
my %dist = map { $_ => INFINITY } keys %$edges;
$dist{$root} = 0;
my %prev = map { $_ => undef } keys %$edges;
my @unsolved = keys %$edges;
my %crossings = (); # node_idx => bool
while (@unsolved) {
# sort unsolved by distance from root
# using a sorting option that accounts for infinity
@unsolved = sort {
$dist{$a} eq INFINITY ? +1 :
$dist{$b} eq INFINITY ? -1 :
$dist{$a} <=> $dist{$b};
} @unsolved;
# we'll solve the closest node
last if $dist{$unsolved[0]} eq INFINITY;
my $n = shift @unsolved;
# stop search
last if $n eq $target;
# now, look at all the nodes connected to n
foreach my $n2 (keys %{$edges->{$n}}) {
# .. and find out if any of their estimated distances
# can be improved if we go through n
if ( ($dist{$n2} eq INFINITY) || ($dist{$n2} > ($dist{$n} + $edges->{$n}{$n2})) ) {
$dist{$n2} = $dist{$n} + $edges->{$n}{$n2};
$prev{$n2} = $n;
$crossings{$n} = 1 if $self->_crossing_edges->{$n}{$n2};
}
}
}
my @points = ();
my $crossings = 0;
{
my $pointmap = $self->_pointmap;
my $u = $target;
while (defined $prev{$u}) {
unshift @points, $pointmap->{$u};
$crossings++ if $crossings{$u};
$u = $prev{$u};
}
}
$self->last_crossings($crossings);
return Slic3r::Polyline->new($from, $new_from, @points, $to); # @points already includes $new_to
}
1;

View File

@ -404,6 +404,10 @@ sub build {
},
],
},
{
title => 'Advanced',
options => [qw(avoid_crossing_perimeters)],
},
]);
$self->add_options_page('Infill', 'shading.png', optgroups => [

View File

@ -7,6 +7,7 @@ our @ISA = qw(Exporter);
our @EXPORT_OK = qw(
PI X Y Z A B X1 Y1 X2 Y2 MIN MAX epsilon slope line_atan lines_parallel
line_point_belongs_to_segment points_coincide distance_between_points
comparable_distance_between_points
line_length midpoint point_in_polygon point_in_segment segment_in_segment
point_is_on_left_of_segment polyline_lines polygon_lines nearest_point
point_along_segment polygon_segment_having_point polygon_has_subsegment
@ -114,6 +115,11 @@ sub distance_between_points {
return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2);
}
sub comparable_distance_between_points {
my ($p1, $p2) = @_;
return (($p1->[X] - $p2->[X])**2) + (($p1->[Y] - $p2->[Y])**2);
}
sub point_line_distance {
my ($point, $line) = @_;
return distance_between_points($point, $line->[A])
@ -251,7 +257,7 @@ sub nearest_point_index {
my ($nearest_point_index, $distance) = ();
for my $i (0..$#$points) {
my $d = distance_between_points($point, $points->[$i]);
my $d = comparable_distance_between_points($point, $points->[$i]);
if (!defined $distance || $d < $distance) {
$nearest_point_index = $i;
$distance = $d;

View File

@ -39,12 +39,14 @@ sub rotate {
my $self = shift;
my ($angle, $center) = @_;
@$self = @{ +(Slic3r::Geometry::rotate_points($angle, $center, $self))[0] };
$self;
}
sub translate {
my $self = shift;
my ($x, $y) = @_;
@$self = @{ +(Slic3r::Geometry::move_points([$x, $y], $self))[0] };
$self;
}
sub x { $_[0]->[0] }

View File

@ -14,6 +14,11 @@ sub lines {
return polygon_lines($self);
}
sub boost_polygon {
my $self = shift;
return Boost::Geometry::Utils::polygon($self);
}
sub boost_linestring {
my $self = shift;
return Boost::Geometry::Utils::linestring([@$self, $self->[0]]);

View File

@ -68,7 +68,7 @@ sub simplify {
my $self = shift;
my $tolerance = shift || 10;
@$self = @{ Slic3r::Geometry::douglas_peucker($self, $tolerance) };
@$self = @{ Boost::Geometry::Utils::linestring_simplify($self, $tolerance) };
bless $_, 'Slic3r::Point' for @$self;
}
@ -115,10 +115,7 @@ sub clip_with_expolygon {
my $self = shift;
my ($expolygon) = @_;
my $result = Boost::Geometry::Utils::polygon_linestring_intersection(
$expolygon->boost_polygon,
$self->boost_linestring,
);
my $result = Boost::Geometry::Utils::polygon_multi_linestring_intersection($expolygon, [$self]);
bless $_, 'Slic3r::Polyline' for @$result;
bless $_, 'Slic3r::Point' for map @$_, @$result;
return @$result;

View File

@ -713,9 +713,29 @@ sub write_gcode {
$Slic3r::Config->print_center->[Y] - (unscale ($print_bb[Y2] - $print_bb[Y1]) / 2) - unscale $print_bb[Y1],
);
# initialize a motion planner for object-to-object travel moves
if ($Slic3r::Config->avoid_crossing_perimeters) {
my $distance_from_objects = 1;
# compute the offsetted convex hull for each object and repeat it for each copy.
my @islands = ();
foreach my $obj_idx (0 .. $#{$self->objects}) {
my @island = Slic3r::ExPolygon->new(convex_hull([
map @{$_->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers},
]))->translate(scale $shift[X], scale $shift[Y])->offset_ex(scale $distance_from_objects, 1, JT_SQUARE);
foreach my $copy (@{ $self->objects->[$obj_idx]->copies }) {
push @islands, map $_->clone->translate(@$copy), @island;
}
}
$gcodegen->external_mp(Slic3r::GCode::MotionPlanner->new(
islands => union_ex([ map @$_, @islands ]),
no_internal => 1,
));
}
# prepare the logic to print one layer
my $skirt_done = 0; # count of skirt layers done
my $brim_done = 0;
my $last_obj_copy = "";
my $extrude_layer = sub {
my ($layer_id, $object_copies) = @_;
my $gcode = "";
@ -758,6 +778,7 @@ sub write_gcode {
}
}
$skirt_done++;
$gcodegen->straight_once(1);
}
# extrude brim
@ -767,10 +788,13 @@ sub write_gcode {
$gcodegen->set_shift(@shift);
$gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim};
$brim_done = 1;
$gcodegen->straight_once(1);
}
for my $obj_copy (@$object_copies) {
my ($obj_idx, $copy) = @$obj_copy;
$gcodegen->new_object(1) if $last_obj_copy && $last_obj_copy ne "${obj_idx}_${copy}";
$last_obj_copy = "${obj_idx}_${copy}";
my $layer = $self->objects->[$obj_idx]->layers->[$layer_id];
$gcodegen->set_shift(map $shift[$_] + unscale $copy->[$_], X,Y);

View File

@ -238,6 +238,7 @@ $j
--toolchange-gcode Load tool-change G-code from the supplied file (default: nothing).
--extra-perimeters Add more perimeters when needed (default: yes)
--randomize-start Randomize starting point across layers (default: yes)
--avoid-crossing-perimeters Optimize travel moves so that no perimeters are crossed (default: no)
--only-retract-when-crossing-perimeters
Disable retraction when travelling between infill paths inside the same island.
(default: no)

View File

@ -32,8 +32,9 @@ use Slic3r;
my $polyline = Slic3r::Polyline->new([
[0,0],[0.5,0.5],[1,0],[1.25,-0.25],[1.5,.5],
]);
$polyline->simplify(0.25);
is_deeply $polyline, [ [0, 0], [0.5, 0.5], [1.25, -0.25], [1.5, 0.5] ], 'Douglas-Peucker';
$polyline->scale(100);
$polyline->simplify(25);
is_deeply $polyline, [ [0, 0], [50, 50], [125, -25], [150, 50] ], 'Douglas-Peucker';
}
{

View File

@ -92,7 +92,7 @@ is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved';
{
my $intersections = $expolygon->clip_line(Slic3r::Line->new(reverse @$line));
is_deeply $intersections, [
[ [20, 15], [16, 15] ],
[ [20, 15], [15, 15] ],
[ [14, 15], [10, 15] ],
], 'reverse line is clipped to square with hole';
}
@ -144,8 +144,8 @@ is_deeply $intersection, [ [12, 12], [18, 16] ], 'internal lines are preserved';
my $intersections = $expolygon->clip_line($line);
is_deeply $intersections, [
[ [152.742, 288.087], [152.742, 215.179], ],
[ [152.742, 108.088], [152.742, 35.1665] ],
[ [152, 287], [152, 214], ],
[ [152, 107], [152, 35] ],
], 'line is clipped to square with hole';
}