From 0eadc5adbae29c1b5cf55c73e0e6970655d5702d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Aug 2012 15:42:58 +0200 Subject: [PATCH 01/16] Manual rebase of the avoid_crossing_perimeters feature --- MANIFEST | 1 + README.markdown | 1 + lib/Slic3r.pm | 1 + lib/Slic3r/Config.pm | 7 + lib/Slic3r/ExPolygon.pm | 3 + lib/Slic3r/GCode.pm | 43 ++++- lib/Slic3r/GCode/MotionPlanner.pm | 259 ++++++++++++++++++++++++++++++ lib/Slic3r/GUI/Tab.pm | 4 + lib/Slic3r/Point.pm | 2 + lib/Slic3r/Polygon.pm | 5 + lib/Slic3r/Print.pm | 46 +++++- slic3r.pl | 1 + 12 files changed, 362 insertions(+), 11 deletions(-) create mode 100644 lib/Slic3r/GCode/MotionPlanner.pm diff --git a/MANIFEST b/MANIFEST index 19de7249..b021f8d8 100644 --- a/MANIFEST +++ b/MANIFEST @@ -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 diff --git a/README.markdown b/README.markdown index fc5ce7f8..0e1d72c7 100644 --- a/README.markdown +++ b/README.markdown @@ -170,6 +170,7 @@ The author of the Silk icon set is Mark James. --layer-gcode Load layer-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) Support material options: --support-material Generate support material for overhangs diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index dc977612..72a08870 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -38,6 +38,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::Line; diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 7af58122..de64fc27 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -462,6 +462,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 the processing times.', + cli => 'avoid-crossing-perimeters!', + type => 'bool', + default => 0, + }, 'support_material' => { label => 'Generate support material', tooltip => 'Enable support material generation.', diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 70371855..717fc1d5 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -155,16 +155,19 @@ sub clip_line { sub simplify { my $self = shift; $_->simplify(@_) for @$self; + $self; } sub translate { my $self = shift; $_->translate(@_) for @$self; + $self; } sub rotate { my $self = shift; $_->rotate(@_) for @$self; + $self; } sub area { diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b5e020ac..273753d4 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -2,7 +2,8 @@ package Slic3r::GCode; use Moo; use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale); +use Slic3r::Geometry qw(PI X Y scale unscale points_coincide); +use Slic3r::Geometry::Clipper qw(union_ex); has 'layer' => (is => 'rw'); has 'shift_x' => (is => 'rw', default => sub {0} ); @@ -10,6 +11,8 @@ has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw', default => sub {0} ); has 'speed' => (is => 'rw'); +has 'motionplanner' => (is => 'rw'); +has 'straight_once' => (is => 'rw'); has 'extruder_idx' => (is => 'rw'); has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds @@ -48,18 +51,35 @@ my %role_speeds = ( &EXTR_ROLE_SUPPORTMATERIAL => 'perimeter', ); -use Slic3r::Geometry qw(points_coincide PI X Y); - sub extruder { my $self = shift; return $Slic3r::extruders->[$self->extruder_idx]; } +sub set_shift { + my $self = shift; + my @shift = @_; + + # adjust last position + $self->last_pos($self->last_pos->clone->translate( + scale($self->shift_x - $shift[X]), + scale($self->shift_y - $shift[Y]), + )); + + $self->shift_x($shift[X]); + $self->shift_y($shift[Y]); +} + sub change_layer { my $self = shift; my ($layer) = @_; $self->layer($layer); + if ($Slic3r::Config->avoid_crossing_perimeters) { + $self->motionplanner(Slic3r::GCode::MotionPlanner->new( + islands => union_ex([ map @{$_->expolygon}, @{$layer->slices} ], undef, 1), + )); + } my $z = $Slic3r::Config->z_offset + $layer->print_z * &Slic3r::SCALING_FACTOR; my $gcode = ""; @@ -145,8 +165,7 @@ sub extrude_path { } # go to first point of extrusion path - $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 if $self->extruder->retracted; @@ -192,6 +211,20 @@ sub extrude_path { return $gcode; } +sub travel_to { + my $self = shift; + my ($point, $comment) = @_; + + return "" if points_coincide($self->last_pos, $point); + if ($Slic3r::Config->avoid_crossing_perimeters && $self->last_pos->distance_to($point) > scale 5 && !$self->straight_once) { + return join '', map $self->G0($_->b, undef, 0, $comment || ""), + $self->motionplanner->shortest_path($self->last_pos, $point)->lines; + } else { + $self->straight_once(0); + return $self->G0($point, undef, 0, $comment || ""); + } +} + sub retract { my $self = shift; my %params = @_; diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm new file mode 100644 index 00000000..cf0d97a2 --- /dev/null +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -0,0 +1,259 @@ +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(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; + + 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, scale Slic3r::Geometry::epsilon)) { + 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; + } + } + } + }; + + for my $i (0 .. $#{$self->islands}) { + $self->islands->[$i]->simplify($self->_inner_margin); + $self->_inner->[$i] = [ $self->islands->[$i]->offset_ex(-$self->_inner_margin) ] + if !$self->no_internal; + $self->_outer->[$i] = [ $self->islands->[$i]->contour->offset($self->_outer_margin) ]; + $_->simplify($self->_inner_margin) for @{$self->_inner->[$i]}, @{$self->_outer->[$i]}; + + 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_linestring_intersection( + $polygon->boost_polygon, + Boost::Geometry::Utils::linestring($line), + )} > 0; + }; + + # lines connecting outer polygons are visible + { + my @outer = (map @$_, @{$self->_outer}); + 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(undef, "space.svg", + lines => \@lines, + 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) = @_; + + # 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; diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 111800e0..1e152b9e 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -392,6 +392,10 @@ sub build { title => 'Horizontal shells', options => [qw(solid_layers)], }, + { + title => 'Advanced', + options => [qw(avoid_crossing_perimeters)], + }, ]); $self->add_options_page('Infill', 'shading.png', optgroups => [ diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index ffa7fa1a..52720b16 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -58,12 +58,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] } diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index fa7ee712..22447890 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -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]]); diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index c5aec01a..df1fc4c0 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -613,9 +613,30 @@ 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 + my $external_motionplanner; + 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 @{$_->expolygon->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers}, + ]))->translate(map -$_, @shift)->offset_ex(scale $distance_from_objects); + foreach my $copy (@{$self->copies->[$obj_idx]}) { + push @islands, map $_->clone->translate(@$copy), @island; + } + } + $external_motionplanner = 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 = ""; @@ -635,8 +656,7 @@ sub write_gcode { # extrude skirt if ($skirt_done < $Slic3r::Config->skirt_height) { - $gcodegen->shift_x($shift[X]); - $gcodegen->shift_y($shift[Y]); + $gcodegen->set_shift(@shift); $gcode .= $gcodegen->set_acceleration($Slic3r::Config->perimeter_acceleration); # skip skirt if we have a large brim if ($layer_id < $Slic3r::Config->skirt_height && ($layer_id != 0 || $Slic3r::Config->skirt_distance + (($Slic3r::Config->skirts - 1) * $Slic3r::flow->spacing) > $Slic3r::Config->brim_width)) { @@ -647,8 +667,7 @@ sub write_gcode { # extrude brim if ($layer_id == 0 && !$brim_done) { - $gcodegen->shift_x($shift[X]); - $gcodegen->shift_y($shift[Y]); + $gcodegen->set_shift(@shift); $gcode .= $gcodegen->extrude_loop($_, 'brim') for @{$self->brim}; $brim_done = 1; } @@ -661,8 +680,21 @@ sub write_gcode { # won't always trigger the automatic retraction $gcode .= $gcodegen->retract; - $gcodegen->shift_x($shift[X] + unscale $copy->[X]); - $gcodegen->shift_y($shift[Y] + unscale $copy->[Y]); + # travel to the first perimeter point using the external motion planner + if ($external_motionplanner && @{ $layer->perimeters } && !$gcodegen->straight_once && $last_obj_copy ne "${obj_idx}_${copy}") { + $gcodegen->set_shift(@shift); + my $layer_mp = $gcodegen->motionplanner; + $gcodegen->motionplanner($external_motionplanner); + my $first_perimeter = $layer->perimeters->[0]->unpack; + my $target = $first_perimeter->polygon->[0]->clone->translate(@$copy); + $gcode .= $gcodegen->travel_to($target, "move to first perimeter point"); + $gcodegen->motionplanner($layer_mp); + } + + $gcodegen->set_shift( + $shift[X] + unscale $copy->[X], + $shift[Y] + unscale $copy->[Y], + ); # extrude perimeters $gcode .= $gcodegen->set_tool($Slic3r::Config->perimeter_extruder-1); @@ -686,6 +718,8 @@ sub write_gcode { $gcode .= $gcodegen->extrude_path($_, 'support material') for $layer->support_fills->shortest_path($gcodegen->last_pos); } + + $last_obj_copy = "${obj_idx}_${copy}"; } return if !$gcode; diff --git a/slic3r.pl b/slic3r.pl index a4f5ca4d..602ebbb6 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -213,6 +213,7 @@ $j --layer-gcode Load layer-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) Support material options: --support-material Generate support material for overhangs From 27090f83bde0bc45a6ff7a43fb9070125ecf6c45 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Aug 2012 17:02:50 +0200 Subject: [PATCH 02/16] Make the motionplanner work again after recent changes to approximation --- lib/Slic3r/ExPolygon.pm | 4 ++-- lib/Slic3r/GCode/MotionPlanner.pm | 22 ++++++++++++++++++---- lib/Slic3r/SVG.pm | 1 - 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 717fc1d5..70e3f4f9 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -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); +use Slic3r::Geometry qw(X Y A B point_in_polygon same_line scale epsilon); use Slic3r::Geometry::Clipper qw(union_ex JT_MITER); # the constructor accepts an array of polygons @@ -113,7 +113,7 @@ sub encloses_line { my ($line) = @_; my $clip = $self->clip_line($line); - return @$clip == 1 && same_line($clip->[0], $line); + return @$clip == 1 && abs($line->length - Slic3r::Geometry::line_length($clip->[0])) < scale epsilon; } sub point_on_segment { diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index cf0d97a2..b95a38ec 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -38,6 +38,7 @@ sub BUILD { 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; @@ -55,13 +56,25 @@ sub BUILD { } }; + # 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], @@ -81,7 +94,7 @@ sub BUILD { my ($polygon, $line) = @_; @{Boost::Geometry::Utils::polygon_linestring_intersection( $polygon->boost_polygon, - Boost::Geometry::Utils::linestring($line), + $line->boost_linestring, )} > 0; }; @@ -148,10 +161,11 @@ sub BUILD { require "Slic3r/SVG.pm"; Slic3r::SVG::output(undef, "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} ], + #polygons => [ map @$_, @{$self->islands} ], + #red_polygons => [ map $_->holes, map @$_, @{$self->_inner} ], + #white_polygons => [ map @$_, @{$self->_outer} ], ); printf "%d islands\n", scalar @{$self->islands}; } diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 1bfa839f..9b00001e 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -13,7 +13,6 @@ sub factor { sub svg { my ($print) = @_; - $print ||= Slic3r::Print->new(x_length => 200 / &Slic3r::SCALING_FACTOR, y_length => 200 / &Slic3r::SCALING_FACTOR); my $svg = SVG->new(width => 200 * 10, height => 200 * 10); my $marker_end = $svg->marker( From c98e9515ed1ec73d220d43e82a3a8cae130aa68d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Aug 2012 21:10:04 +0200 Subject: [PATCH 03/16] Better implementation of the external motion planner --- lib/Slic3r/ExPolygon.pm | 12 +++++++---- lib/Slic3r/GCode.pm | 33 +++++++++++++++++++++++++------ lib/Slic3r/GCode/MotionPlanner.pm | 2 +- lib/Slic3r/Print.pm | 27 ++++++++++--------------- 4 files changed, 46 insertions(+), 28 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 70e3f4f9..5d87f720 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -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 scale epsilon); +use Slic3r::Geometry qw(X Y A B point_in_polygon same_line line_length scale epsilon); use Slic3r::Geometry::Clipper qw(union_ex JT_MITER); # the constructor accepts an array of polygons @@ -110,10 +110,14 @@ sub encloses_point_quick { sub encloses_line { my $self = shift; - my ($line) = @_; - + my ($line, $tolerance) = @_; my $clip = $self->clip_line($line); - return @$clip == 1 && abs($line->length - Slic3r::Geometry::line_length($clip->[0])) < scale epsilon; + if (!defined $tolerance) { + # optimization + return @$clip == 1 && same_line($clip->[0], $line); + } else { + return @$clip == 1 && abs(line_length($clip->[0]) - $line->length) < $tolerance; + } } sub point_on_segment { diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 273753d4..842cf2b0 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -11,8 +11,10 @@ has 'shift_y' => (is => 'rw', default => sub {0} ); has 'z' => (is => 'rw', default => sub {0} ); has 'speed' => (is => 'rw'); -has 'motionplanner' => (is => 'rw'); -has 'straight_once' => (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_idx' => (is => 'rw'); has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'elapsed_time' => (is => 'rw', default => sub {0} ); # seconds @@ -76,7 +78,7 @@ sub change_layer { $self->layer($layer); if ($Slic3r::Config->avoid_crossing_perimeters) { - $self->motionplanner(Slic3r::GCode::MotionPlanner->new( + $self->layer_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @{$_->expolygon}, @{$layer->slices} ], undef, 1), )); } @@ -216,13 +218,32 @@ sub travel_to { my ($point, $comment) = @_; return "" if points_coincide($self->last_pos, $point); + + my $gcode = ""; if ($Slic3r::Config->avoid_crossing_perimeters && $self->last_pos->distance_to($point) > scale 5 && !$self->straight_once) { - return join '', map $self->G0($_->b, undef, 0, $comment || ""), - $self->motionplanner->shortest_path($self->last_pos, $point)->lines; + 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) { + my @shift = ($self->shift_x, $self->shift_y); + $self->set_shift(0,0); + $point->translate(map scale $_, @shift); + $gcode .= $plan->($self->external_mp); + $self->new_object(0); + $self->set_shift(@shift); + } else { + $gcode .= $plan->($self->layer_mp); + } } else { $self->straight_once(0); - return $self->G0($point, undef, 0, $comment || ""); + $gcode .= $self->G0($point, undef, 0, $comment || ""); } + + return $gcode; } sub retract { diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index b95a38ec..ef55f16c 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -45,7 +45,7 @@ sub BUILD { 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, scale Slic3r::Geometry::epsilon)) { + 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; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index df1fc4c0..3f2b1769 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -614,7 +614,6 @@ sub write_gcode { ); # initialize a motion planner for object-to-object travel moves - my $external_motionplanner; 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. @@ -622,15 +621,15 @@ sub write_gcode { foreach my $obj_idx (0 .. $#{$self->objects}) { my @island = Slic3r::ExPolygon->new(convex_hull([ map @{$_->expolygon->contour}, map @{$_->slices}, @{$self->objects->[$obj_idx]->layers}, - ]))->translate(map -$_, @shift)->offset_ex(scale $distance_from_objects); + ]))->translate(scale $shift[X], scale $shift[Y])->offset_ex(scale $distance_from_objects, 1, JT_SQUARE); foreach my $copy (@{$self->copies->[$obj_idx]}) { push @islands, map $_->clone->translate(@$copy), @island; } } - $external_motionplanner = Slic3r::GCode::MotionPlanner->new( + $gcodegen->external_mp(Slic3r::GCode::MotionPlanner->new( islands => union_ex([ map @$_, @islands ]), no_internal => 1, - ); + )); } # prepare the logic to print one layer @@ -663,6 +662,7 @@ sub write_gcode { $gcode .= $gcodegen->extrude_loop($_, 'skirt') for @{$self->skirt}; } $skirt_done++; + $gcodegen->straight_once(1); } # extrude brim @@ -670,27 +670,18 @@ 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}"; my $layer = $self->objects->[$obj_idx]->layers->[$layer_id]; # retract explicitely because changing the shift_[xy] properties below # won't always trigger the automatic retraction $gcode .= $gcodegen->retract; - # travel to the first perimeter point using the external motion planner - if ($external_motionplanner && @{ $layer->perimeters } && !$gcodegen->straight_once && $last_obj_copy ne "${obj_idx}_${copy}") { - $gcodegen->set_shift(@shift); - my $layer_mp = $gcodegen->motionplanner; - $gcodegen->motionplanner($external_motionplanner); - my $first_perimeter = $layer->perimeters->[0]->unpack; - my $target = $first_perimeter->polygon->[0]->clone->translate(@$copy); - $gcode .= $gcodegen->travel_to($target, "move to first perimeter point"); - $gcodegen->motionplanner($layer_mp); - } - $gcodegen->set_shift( $shift[X] + unscale $copy->[X], $shift[Y] + unscale $copy->[Y], @@ -773,8 +764,10 @@ sub write_gcode { # this happens before Z goes down to layer 0 again, so that # no collision happens hopefully. if ($finished_objects > 0) { - $gcodegen->shift_x($shift[X] + unscale $copy->[X]); - $gcodegen->shift_y($shift[Y] + unscale $copy->[Y]); + $gcodegen->set_shift( + $shift[X] + unscale $copy->[X], + $shift[Y] + unscale $copy->[Y], + ); print $fh $gcodegen->retract; print $fh $gcodegen->G0(Slic3r::Point->new(0,0), undef, 0, 'move to origin position for next object'); } From 56a118986ff55fc5d3b68c10276edbc66b040e28 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Thu, 23 Aug 2012 21:19:10 +0200 Subject: [PATCH 04/16] External motion was crossing still too much --- lib/Slic3r/GCode/MotionPlanner.pm | 16 +++++++++++++--- 1 file changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index ef55f16c..4957f434 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -12,7 +12,7 @@ has '_edges' => (is => 'rw', default => sub { {} }); # node_idx => { nod has '_crossing_edges' => (is => 'rw', default => sub { {} }); # edge_idx => bool use List::Util qw(first); -use Slic3r::Geometry qw(scale epsilon nearest_point); +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 @@ -98,9 +98,19 @@ sub BUILD { )} > 0; }; - # lines connecting outer polygons are visible { 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]}) { @@ -163,7 +173,7 @@ sub BUILD { lines => \@lines, points => [ values %{$self->_pointmap} ], no_arrows => 1, - #polygons => [ map @$_, @{$self->islands} ], + polygons => [ map @$_, @{$self->islands} ], #red_polygons => [ map $_->holes, map @$_, @{$self->_inner} ], #white_polygons => [ map @$_, @{$self->_outer} ], ); From 2f1ae3f112430b852f91653992e5ce323b05471f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 21 Sep 2012 15:46:40 +0200 Subject: [PATCH 05/16] Restore non-manifold warnings --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 5f83117c..ecf31e8f 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -309,6 +309,7 @@ sub load_file { : [0,0], ], ); + $object->mesh->check_manifoldness; # we only consider the rotation of the first instance for now $object->set_rotation($model->objects->[$i]->instances->[0]->rotation) From e21fdf0b15b12423a789be6d8933b3c1448195c0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 21 Sep 2012 15:47:38 +0200 Subject: [PATCH 06/16] Little cleanup --- lib/Slic3r/Model.pm | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index 2d7a3315..aba13f23 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -83,11 +83,6 @@ sub add_instance { sub mesh { my $self = shift; - my $vertices = []; - my $facets = []; - - - return Slic3r::TriangleMesh->new( vertices => $self->vertices, facets => [ map @{$_->facets}, @{$self->volumes} ], From 91e9f651b51e6288e3f2678c00149d69ca3706a3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 22 Sep 2012 15:51:18 +0200 Subject: [PATCH 07/16] Bugfix: --avoid-crossing-perimeters was crashing when processing empty layers --- lib/Slic3r/GCode/MotionPlanner.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 4957f434..7bd84b4f 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -224,6 +224,8 @@ 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); From f1230312e3ee8a3eb5d157f8b6223829e2d56f7c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 26 Jan 2013 23:55:47 +0100 Subject: [PATCH 08/16] Fixes for merge issues --- lib/Slic3r/GCode.pm | 12 +++++------- lib/Slic3r/GCode/MotionPlanner.pm | 2 +- lib/Slic3r/Print.pm | 1 + 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 35a878ea..9f92b912 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -163,8 +163,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; @@ -205,9 +204,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; @@ -265,13 +262,14 @@ sub travel_to { 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) { + $point = $point->clone; my $plan = sub { my $mp = shift; return join '', - map $self->G0($_->b, undef, 0, $comment || ""), + map $self->G0($_->[B], undef, 0, $comment || ""), $mp->shortest_path($self->last_pos, $point)->lines; }; diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 7bd84b4f..810a02ae 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -169,7 +169,7 @@ sub BUILD { } require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "space.svg", + Slic3r::SVG::output("space.svg", lines => \@lines, points => [ values %{$self->_pointmap} ], no_arrows => 1, diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 0bfdeec0..5f06cf1d 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -794,6 +794,7 @@ sub write_gcode { 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); From ad48fdc7f96f2bcf6e47055f9403799fe887f78b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 27 Jan 2013 12:48:16 +0100 Subject: [PATCH 09/16] Fix external motionplanner too --- lib/Slic3r/GCode.pm | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 9f92b912..d503db99 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -63,8 +63,8 @@ sub set_shift { my @shift = @_; $self->last_pos->translate( - scale ($shift[X] - $self->shift_x), - scale ($shift[Y] - $self->shift_y), + scale ($self->shift_x - $shift[X]), + scale ($self->shift_y - $shift[Y]), ); $self->shift_x($shift[X]); @@ -265,7 +265,6 @@ sub travel_to { $self->speed('travel'); my $gcode = ""; if ($Slic3r::Config->avoid_crossing_perimeters && $self->last_pos->distance_to($point) > scale 5 && !$self->straight_once) { - $point = $point->clone; my $plan = sub { my $mp = shift; return join '', @@ -274,11 +273,16 @@ sub travel_to { }; if ($self->new_object) { - my @shift = ($self->shift_x, $self->shift_y); - $self->set_shift(0,0); - $point->translate(map scale $_, @shift); - $gcode .= $plan->($self->external_mp); $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); From ddaeaa7591a196a967d24c93eb32e8e1d088addb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 27 Jan 2013 13:06:45 +0100 Subject: [PATCH 10/16] Fix last_pos shifting and add unit test --- MANIFEST | 1 + lib/Slic3r/GCode.pm | 1 + t/gcode.t | 20 ++++++++++++++++++++ 3 files changed, 22 insertions(+) create mode 100644 t/gcode.t diff --git a/MANIFEST b/MANIFEST index 6deab070..e48376de 100644 --- a/MANIFEST +++ b/MANIFEST @@ -59,6 +59,7 @@ t/collinear.t t/custom_gcode.t t/dynamic.t t/fill.t +t/gcode.t t/geometry.t t/layers.t t/loops.t diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d503db99..3633c3c5 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -62,6 +62,7 @@ sub set_shift { my $self = shift; my @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_y - $shift[Y]), diff --git a/t/gcode.t b/t/gcode.t new file mode 100644 index 00000000..c41307d4 --- /dev/null +++ b/t/gcode.t @@ -0,0 +1,20 @@ +use Test::More tests => 1; +use strict; +use warnings; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Geometry qw(scale); + +{ + local $Slic3r::Config = Slic3r::Config->new_from_defaults; + my $gcodegen = Slic3r::GCode->new(layer_count => 1); + $gcodegen->set_shift(10, 10); + is_deeply $gcodegen->last_pos, [scale -10, scale -10], 'last_pos is shifted correctly'; +} + +__END__ From 6cb891f2db7788c0fc5755e48c1987299be12f3e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 00:09:19 +0100 Subject: [PATCH 11/16] Use Boost::Geometry::Utils for Douglas-Peucker --- lib/Slic3r/Polyline.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 6e349754..11047240 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -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; } From ae201c8f41a60b7de9776e94b088938362b821b5 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 00:18:55 +0100 Subject: [PATCH 12/16] Optimization: avoid calculating square roots if not needed --- lib/Slic3r/Geometry.pm | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index db3ecb6c..71518951 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -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; From 2963e54d21a19c90daaff32e2589defed6f5856b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 00:32:19 +0100 Subject: [PATCH 13/16] Use the faster polygon_multi_linestring_intersection() --- lib/Slic3r/ExPolygon.pm | 5 +---- lib/Slic3r/Fill/Rectilinear.pm | 6 +++--- lib/Slic3r/GCode/MotionPlanner.pm | 5 +---- lib/Slic3r/Polyline.pm | 5 +---- 4 files changed, 6 insertions(+), 15 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 9a198c48..383a6d82 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -153,10 +153,7 @@ 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 { diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index ea9f1ddb..eb633a74 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -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; diff --git a/lib/Slic3r/GCode/MotionPlanner.pm b/lib/Slic3r/GCode/MotionPlanner.pm index 810a02ae..d55faeb2 100644 --- a/lib/Slic3r/GCode/MotionPlanner.pm +++ b/lib/Slic3r/GCode/MotionPlanner.pm @@ -92,10 +92,7 @@ sub BUILD { my $intersects = sub { my ($polygon, $line) = @_; - @{Boost::Geometry::Utils::polygon_linestring_intersection( - $polygon->boost_polygon, - $line->boost_linestring, - )} > 0; + @{Boost::Geometry::Utils::polygon_multi_linestring_intersection([$polygon], [$line])} > 0; }; { diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index 11047240..9d9926c4 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -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; From 721b61e798623178a8ce3c30894e1640979c83f6 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 00:34:09 +0100 Subject: [PATCH 14/16] Require Boost::Geometry::Utils 0.06 --- Build.PL | 2 +- lib/Slic3r.pm | 1 + 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/Build.PL b/Build.PL index 89af11ed..b4bed05c 100644 --- a/Build.PL +++ b/Build.PL @@ -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', diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index f6653ea4..f4e26b98 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -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; From 4d70748723163a5330b3f46078c88a3e54eefc57 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 00:40:08 +0100 Subject: [PATCH 15/16] Make tests happy about the new Boost::Geometry::Utils working with integers --- t/clean_polylines.t | 5 +++-- t/polyclip.t | 6 +++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/t/clean_polylines.t b/t/clean_polylines.t index bc7d864e..064b7bd3 100644 --- a/t/clean_polylines.t +++ b/t/clean_polylines.t @@ -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'; } { diff --git a/t/polyclip.t b/t/polyclip.t index 285ef45b..49e3be17 100644 --- a/t/polyclip.t +++ b/t/polyclip.t @@ -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'; } From e8cb1f4528d3f0fa82ccd0c32ac8b86408ac3d2f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 28 Jan 2013 10:42:24 +0100 Subject: [PATCH 16/16] Slight addition to avoid_crossing_perimeters tooltip --- lib/Slic3r/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index a8653ab8..f57eff59 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -526,7 +526,7 @@ our $Options = { }, '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 the processing times.', + 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,