diff --git a/MANIFEST b/MANIFEST index 36efa4b7f..b80c4a829 100644 --- a/MANIFEST +++ b/MANIFEST @@ -41,6 +41,7 @@ lib/Slic3r/TriangleMesh/IntersectionLine.pm MANIFEST This list of files README.markdown slic3r.pl +t/angles.t t/arcs.t t/clean_polylines.t t/clipper.t diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 60a6db13c..90e1187ca 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -53,7 +53,7 @@ sub make_fill { { my @surfaces_with_bridge_angle = grep defined $_->bridge_angle, @{$layer->fill_surfaces}; foreach my $group (Slic3r::Surface->group({merge_solid => 1}, @{$layer->fill_surfaces})) { - my $union = union_ex([ map $_->p, @$group ]); + my $union = union_ex([ map $_->p, @$group ], undef, 1); # subtract surfaces having a defined bridge_angle from any other if (@surfaces_with_bridge_angle && !defined $group->[0]->bridge_angle) { diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 2b88789cd..8db61a322 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -14,10 +14,11 @@ our @EXPORT_OK = qw( rotate_points move_points remove_coinciding_points clip_segment_polygon sum_vectors multiply_vector subtract_vectors dot perp polygon_points_visibility line_intersection bounding_box bounding_box_intersect same_point - longest_segment angle3points three_points_aligned + longest_segment angle3points three_points_aligned line_direction polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges shortest_path collinear scale unscale merge_collinear_lines + rad2deg_dir ); use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker); @@ -52,6 +53,14 @@ sub line_atan { return atan2($line->[B][Y] - $line->[A][Y], $line->[B][X] - $line->[A][X]); } +sub line_direction { + my ($line) = @_; + my $atan2 = line_atan($line); + return ($atan2 == PI) ? 0 + : ($atan2 < 0) ? ($atan2 + PI) + : $atan2; +} + sub lines_parallel { my ($line1, $line2) = @_; @@ -311,6 +320,13 @@ sub rad2deg { return $rad / PI() * 180; } +sub rad2deg_dir { + my ($rad) = @_; + $rad = ($rad < PI) ? (-$rad + PI/2) : ($rad + PI/2); + $rad += PI if $rad < 0; + return rad2deg($rad); +} + sub rotate_points { my ($radians, $center, @points) = @_; $center ||= [0,0]; diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 8cb0069fe..05e7b6c10 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -9,6 +9,7 @@ our @EXPORT_OK = qw(explode_expolygon explode_expolygons safety_offset offset is_counter_clockwise); use Math::Clipper 1.02 ':all'; +use Slic3r::Geometry qw(scale); our $clipper = Math::Clipper->new; sub explode_expolygon { @@ -23,15 +24,15 @@ sub explode_expolygons { sub safety_offset { my ($polygons) = @_; - return Math::Clipper::offset($polygons, 100, 100, JT_MITER, 2); + return Math::Clipper::offset($polygons, scale 1e-05, 100, JT_MITER, 2); } sub diff_ex { - my ($subject, $clip) = @_; + my ($subject, $clip, $safety_offset) = @_; $clipper->clear; $clipper->add_subject_polygons($subject); - $clipper->add_clip_polygons($clip); + $clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip); return [ map Slic3r::ExPolygon->new($_), @{ $clipper->ex_execute(CT_DIFFERENCE, PFT_NONZERO, PFT_NONZERO) }, @@ -43,10 +44,10 @@ sub diff { } sub union_ex { - my ($polygons, $jointype) = @_; + my ($polygons, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; $clipper->clear; - $clipper->add_subject_polygons($polygons); + $clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons); return [ map Slic3r::ExPolygon->new($_), @{ $clipper->ex_execute(CT_UNION, $jointype, $jointype) }, @@ -54,11 +55,11 @@ sub union_ex { } sub intersection_ex { - my ($subject, $clip, $jointype) = @_; + my ($subject, $clip, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; $clipper->clear; $clipper->add_subject_polygons($subject); - $clipper->add_clip_polygons($clip); + $clipper->add_clip_polygons($safety_offset ? safety_offset($clip) : $clip); return [ map Slic3r::ExPolygon->new($_), @{ $clipper->ex_execute(CT_INTERSECTION, $jointype, $jointype) }, diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 9fe78fe7d..0a43f07ed 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -2,7 +2,7 @@ package Slic3r::Layer; use Moo; use Math::Clipper ':all'; -use Slic3r::Geometry qw(scale collinear X Y A B PI); +use Slic3r::Geometry qw(scale collinear X Y A B PI rad2deg_dir); use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex is_counter_clockwise); use XXX; @@ -224,7 +224,7 @@ sub process_bridges { # offset the contour and intersect it with the internal surfaces to discover # which of them has contact with our bridge my @supporting_surfaces = (); - my ($contour_offset) = $expolygon->contour->offset($Slic3r::flow_width / $Slic3r::resolution); + my ($contour_offset) = $expolygon->contour->offset(scale $Slic3r::flow_width * sqrt(2)); foreach my $internal_surface (@internal_surfaces) { my $intersection = intersection_ex([$contour_offset], [$internal_surface->contour->p]); if (@$intersection) { @@ -236,7 +236,7 @@ sub process_bridges { require "Slic3r/SVG.pm"; Slic3r::SVG::output(undef, "bridge.svg", green_polygons => [ map $_->p, @supporting_surfaces ], - red_polygons => [ @$expolygon ], + #red_polygons => [ @$expolygon ], ); } @@ -257,6 +257,7 @@ sub process_bridges { $bridge_over_hole = 1; } else { foreach my $edge (@surface_edges) { + next unless @{$edge->points} >= 4; shift @{$edge->points}; pop @{$edge->points}; } @@ -272,15 +273,21 @@ sub process_bridges { Slic3r::SVG::output(undef, "bridge.svg", polylines => [ map $_->p, @edges ], ); - exit if $self->id == 30; } - if (@edges == 2) { - my @chords = map Slic3r::Line->new($_->points->[0], $_->points->[-1]), @edges; - my @midpoints = map $_->midpoint, @chords; - $bridge_angle = -Slic3r::Geometry::rad2deg(Slic3r::Geometry::line_atan(\@midpoints) + PI/2); - Slic3r::debugf "Optimal infill angle of bridge on layer %d is %d degrees\n", $self->id, $bridge_angle; + { + my $weighted_sum = 0; + my $total_length = 0; + foreach my $line (map $_->lines, @edges) { + my $len = $line->length; + $weighted_sum += $len * $line->direction; + $total_length += $len; + } + $bridge_angle = rad2deg_dir(($weighted_sum / $total_length) + PI/2); } + + Slic3r::debugf "Optimal infill angle of bridge on layer %d is %d degrees\n", + $self->id, $bridge_angle if defined $bridge_angle; } # now, extend our bridge by taking a portion of supporting surfaces diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 6d395e50b..541689d24 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -85,6 +85,11 @@ sub atan { return Slic3r::Geometry::line_atan($self); } +sub direction { + my $self = shift; + return Slic3r::Geometry::line_direction($self); +} + sub intersection { my $self = shift; my ($line, $require_crossing) = @_; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index fa037877c..c820f0b2e 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -3,7 +3,7 @@ use Moo; use Math::Clipper qw(); use Slic3r::Geometry qw(A B polyline_remove_parallel_continuous_edges polyline_remove_acute_vertices - polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges move_points); + polygon_remove_acute_vertices polygon_remove_parallel_continuous_edges move_points same_point); use Sub::Quote; use XXX; @@ -156,9 +156,14 @@ sub clip_with_expolygon { push @polylines, $current_polyline; } - if (@polylines > 1 && scalar(@{$polylines[-1]}) == 2 && $polylines[-1][-1] eq $polylines[0][0]) { - unshift @{$polylines[0]}, $polylines[-1][0]; - pop @polylines; + if (@polylines > 1 && same_point($polylines[-1][-1], $polylines[0][0])) { + if (scalar(@{$polylines[-1]}) == 2) { + unshift @{$polylines[0]}, $polylines[-1][0]; + pop @polylines; + } else { + push @{$polylines[-1]}, $polylines[0][-1]; + shift @polylines; + } } return map Slic3r::Polyline->cast($_), @polylines; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 3cee61b8b..f6fc17cbd 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -177,6 +177,7 @@ sub detect_surfaces_type { my $expolygons = diff_ex( [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$subject_surfaces ], [ map { ref $_ eq 'ARRAY' ? $_ : ref $_ eq 'Slic3r::ExPolygon' ? @$_ : $_->p } @$clip_surfaces ], + 1, ); return grep $_->contour->is_printable, map Slic3r::Surface->cast_from_expolygon($_, surface_type => $result_type), @@ -206,7 +207,6 @@ sub detect_surfaces_type { # of current layer and upper one) if ($upper_layer) { @top = $surface_difference->($layer->surfaces, $upper_layer->surfaces, 'top'); - } else { # if no upper layer, all surfaces of this one are solid @top = @{$layer->surfaces}; @@ -217,22 +217,6 @@ sub detect_surfaces_type { # of current layer and lower one) if ($lower_layer) { @bottom = $surface_difference->($layer->surfaces, $lower_layer->surfaces, 'bottom'); - - $_->contour->merge_continuous_lines for @bottom; - - # merge_continuous_lines could return polylines with less than 3 points (thus invalid) - # actually, this shouldn't happen so it deserves further investigation - @bottom = grep $_->contour->is_valid, @bottom; - - foreach my $surface (@bottom) { - $surface->contour->remove_acute_vertices; - - # okay, this is an Ugly Hack(tm) to avoid floating point math problems - # with diagonal bridges. will find a nicer solution, promised. - my $offset = safety_offset([$surface->contour->p]); - @{$surface->contour->points} = map Slic3r::Point->new($_), @{ $offset->[0] }; - } - } else { # if no lower layer, all surfaces of this one are solid @bottom = @{$layer->surfaces}; @@ -391,7 +375,6 @@ sub infill_every_layers { [ map $_->p, grep $_->surface_type eq 'internal', @{$layer->fill_surfaces} ], ); next if !@$intersection; - my $intersection_offsetted = safety_offset([ map @$_, @$intersection ]); # new fill surfaces of the current layer are: # - any non-internal surface @@ -414,7 +397,8 @@ sub infill_every_layers { map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth, @{$layer->fill_surfaces}, ], - $intersection_offsetted, + $intersection, + 1, )}; } @{$layer->fill_surfaces} = @new_surfaces; @@ -435,7 +419,8 @@ sub infill_every_layers { map $_->p, grep $_->surface_type eq 'internal' && $_->depth_layers == $depth, @{$lower_layer->fill_surfaces}, ], - $intersection_offsetted, + $intersection, + 1, )}; } @{$lower_layer->fill_surfaces} = @new_surfaces; diff --git a/t/angles.t b/t/angles.t new file mode 100644 index 000000000..551c0a4c2 --- /dev/null +++ b/t/angles.t @@ -0,0 +1,55 @@ +use Test::More; +use strict; +use warnings; + +plan tests => 23; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; +use Slic3r::Geometry qw(line_atan line_direction rad2deg_dir PI); + +#========================================================== + +{ + is line_atan([ [0, 0], [10, 0] ]), (0), 'E atan2'; + is line_atan([ [10, 0], [0, 0] ]), (PI), 'W atan2'; + is line_atan([ [0, 0], [0, 10] ]), (PI/2), 'N atan2'; + is line_atan([ [0, 10], [0, 0] ]), -(PI/2), 'S atan2'; + + is line_atan([ [10, 10], [0, 0] ]), -(PI*3/4), 'SW atan2'; + is line_atan([ [0, 0], [10, 10] ]), (PI*1/4), 'NE atan2'; + is line_atan([ [0, 10], [10, 0] ]), -(PI*1/4), 'SE atan2'; + is line_atan([ [10, 0], [0, 10] ]), (PI*3/4), 'NW atan2'; +} + +#========================================================== + +{ + is line_direction([ [0, 0], [10, 0] ]), (0), 'E direction'; + is line_direction([ [10, 0], [0, 0] ]), (0), 'W direction'; + is line_direction([ [0, 0], [0, 10] ]), (PI/2), 'N direction'; + is line_direction([ [0, 10], [0, 0] ]), (PI/2), 'S direction'; + + is line_direction([ [10, 10], [0, 0] ]), (PI*1/4), 'SW direction'; + is line_direction([ [0, 0], [10, 10] ]), (PI*1/4), 'NE direction'; + is line_direction([ [0, 10], [10, 0] ]), (PI*3/4), 'SE direction'; + is line_direction([ [10, 0], [0, 10] ]), (PI*3/4), 'NW direction'; +} + +#========================================================== + +{ + is rad2deg_dir(0), 90, 'E (degrees)'; + is rad2deg_dir(PI), 270, 'W (degrees)'; + is rad2deg_dir(PI/2), 0, 'N (degrees)'; + is rad2deg_dir(-(PI/2)), 180, 'S (degrees)'; + is rad2deg_dir(PI*1/4), 45, 'NE (degrees)'; + is rad2deg_dir(PI*3/4), 135, 'NW (degrees)'; + is rad2deg_dir(PI/6), 60, '30°'; +} + +#==========================================================