diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 8db61a3220..2b60b280a0 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -5,7 +5,7 @@ use warnings; require Exporter; our @ISA = qw(Exporter); our @EXPORT_OK = qw( - PI X Y Z A B X1 Y1 X2 Y2 epsilon slope line_atan lines_parallel + 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 line_length midpoint point_in_polygon point_in_segment segment_in_segment point_is_on_left_of_segment polyline_lines polygon_lines nearest_point @@ -18,7 +18,7 @@ our @EXPORT_OK = qw( 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 + rad2deg_dir bounding_box_center ); use Slic3r::Geometry::DouglasPeucker qw(Douglas_Peucker); @@ -34,6 +34,8 @@ use constant X1 => 0; use constant Y1 => 1; use constant X2 => 2; use constant Y2 => 3; +use constant MIN => 0; +use constant MAX => 1; our $parallel_degrees_limit = abs(deg2rad(3)); our $epsilon = 1E-6; @@ -603,6 +605,14 @@ sub bounding_box { return ($x[0], $y[0], $x[-1], $y[-1]); } +sub bounding_box_center { + my @bounding_box = bounding_box(@_); + return Slic3r::Point->new( + ($bounding_box[X2] - $bounding_box[X1]) / 2, + ($bounding_box[Y2] - $bounding_box[Y1]) / 2, + ); +} + # bounding_box_intersect($d, @a, @b) # Return true if the given bounding boxes @a and @b intersect # in $d dimensions. Used by line_intersection(). diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 0a43f07ede..9f55487b84 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 rad2deg_dir); +use Slic3r::Geometry qw(scale collinear X Y A B PI rad2deg_dir bounding_box_center); use Slic3r::Geometry::Clipper qw(union_ex diff_ex intersection_ex is_counter_clockwise); use XXX; @@ -24,6 +24,21 @@ has 'lines' => ( ); # collection of surfaces generated by slicing the original geometry +has 'slices' => ( + is => 'rw', + #isa => 'ArrayRef[Slic3r::Surface]', + default => sub { [] }, +); + +# collection of surfaces generated by offsetting the innermost perimeter(s) +# they represent boundaries of areas to fill +has 'fill_boundaries' => ( + is => 'rw', + #isa => 'ArrayRef[Slic3r::Surface]', + default => sub { [] }, +); + +# collection of surfaces generated by clipping the slices to the fill boundaries has 'surfaces' => ( is => 'rw', #isa => 'ArrayRef[Slic3r::Surface]', @@ -51,14 +66,6 @@ has 'skirts' => ( default => sub { [] }, ); -# collection of surfaces generated by offsetting the innermost perimeter(s) -# they represent boundaries of areas to fill -has 'fill_boundaries' => ( - is => 'rw', - #isa => 'ArrayRef[Slic3r::Surface]', - default => sub { [] }, -); - # ordered collection of extrusion paths to fill surfaces has 'fills' => ( is => 'rw', @@ -110,7 +117,7 @@ sub make_surfaces { Slic3r::debugf " %d surface(s) having %d holes detected from %d polylines\n", scalar(@$expolygons), scalar(map $_->holes, @$expolygons), scalar(@$loops); - push @{$self->surfaces}, + push @{$self->slices}, map Slic3r::Surface->cast_from_expolygon($_, surface_type => 'internal'), @$expolygons; } @@ -162,9 +169,8 @@ sub prepare_fill_surfaces { sub remove_small_surfaces { my $self = shift; - my @good_surfaces = (); - my $distance = ($Slic3r::flow_width / 2 / $Slic3r::resolution); + my $distance = scale $Slic3r::flow_width / 2; my @surfaces = @{$self->fill_surfaces}; @{$self->fill_surfaces} = (); @@ -174,18 +180,26 @@ sub remove_small_surfaces { # offset the results outwards again and merge the results @offsets = map $_->offset_ex($distance), @offsets; - @offsets = @{ union_ex([ map @$_, @offsets ]) }; - - # the difference between $surface->expolygon and @offsets - # is what we can't print since it's too small + @offsets = @{ union_ex([ map @$_, @offsets ], undef, 1) }; push @{$self->fill_surfaces}, map Slic3r::Surface->cast_from_expolygon($_, surface_type => $surface->surface_type), @offsets; } - Slic3r::debugf "removed %d small surfaces at layer %d\n", + Slic3r::debugf "identified %d small surfaces at layer %d\n", (@surfaces - @{$self->fill_surfaces}), $self->id if @{$self->fill_surfaces} != @surfaces; + + # the difference between @surfaces and $self->fill_surfaces + # is what's too small; we add it back as solid infill + { + my $diff = diff_ex( + [ map $_->p, @surfaces ], + [ map $_->p, @{$self->fill_surfaces} ], + ); + push @{$self->fill_surfaces}, map Slic3r::Surface->cast_from_expolygon($_, + surface_type => 'internal-solid'), @$diff; + } } sub remove_small_perimeters { @@ -215,7 +229,7 @@ sub process_bridges { ($_->surface_type eq 'bottom' && $self->id > 0) || $_->surface_type eq 'top' } @{$self->fill_surfaces} or return; - my @internal_surfaces = grep $_->surface_type =~ /internal/, @{$self->surfaces}; + my @internal_surfaces = grep $_->surface_type =~ /internal/, @{$self->slices}; SURFACE: foreach my $surface (@solid_surfaces) { my $expolygon = $surface->expolygon->safety_offset; @@ -234,16 +248,17 @@ sub process_bridges { if (0) { require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "bridge.svg", + Slic3r::SVG::output(undef, "bridge_surfaces.svg", green_polygons => [ map $_->p, @supporting_surfaces ], - #red_polygons => [ @$expolygon ], + red_polygons => [ @$expolygon ], ); } - next SURFACE unless @supporting_surfaces; - Slic3r::debugf " Found $description on layer %d with %d support(s)\n", + Slic3r::debugf "Found $description on layer %d with %d support(s)\n", $self->id, scalar(@supporting_surfaces); + next SURFACE unless @supporting_surfaces; + my $bridge_angle = undef; if ($surface->surface_type eq 'bottom') { # detect optimal bridge angle @@ -256,37 +271,34 @@ sub process_bridges { && @{$supporting_surface->contour->p} == @{$surface_edges[0]->p}) { $bridge_over_hole = 1; } else { - foreach my $edge (@surface_edges) { - next unless @{$edge->points} >= 4; - shift @{$edge->points}; - pop @{$edge->points}; - } @surface_edges = grep { @{$_->points} } @surface_edges; } push @edges, @surface_edges; } - Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges); - Slic3r::debugf " and covers a hole\n" if $bridge_over_hole; + Slic3r::debugf " Bridge is supported on %d edge(s)\n", scalar(@edges); + Slic3r::debugf " and covers a hole\n" if $bridge_over_hole; if (0) { require "Slic3r/SVG.pm"; - Slic3r::SVG::output(undef, "bridge.svg", + Slic3r::SVG::output(undef, "bridge_edges.svg", polylines => [ map $_->p, @edges ], ); } - { - my $weighted_sum = 0; - my $total_length = 0; - foreach my $line (map $_->lines, @edges) { + if (@edges) { + my $center = bounding_box_center([ map @{$_->points}, @edges ]); + my $x = my $y = 0; + foreach my $point (map @{$_->points}, @edges) { + my $line = Slic3r::Line->new($center, $point); + my $dir = $line->direction; my $len = $line->length; - $weighted_sum += $len * $line->direction; - $total_length += $len; + $x += cos($dir) * $len; + $y += sin($dir) * $len; } - $bridge_angle = rad2deg_dir(($weighted_sum / $total_length) + PI/2); + $bridge_angle = rad2deg_dir(atan2($y, $x)); } - Slic3r::debugf "Optimal infill angle of bridge on layer %d is %d degrees\n", + Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", $self->id, $bridge_angle if defined $bridge_angle; } diff --git a/lib/Slic3r/Perimeter.pm b/lib/Slic3r/Perimeter.pm index e4d8f1ce50..99feac9275 100644 --- a/lib/Slic3r/Perimeter.pm +++ b/lib/Slic3r/Perimeter.pm @@ -2,7 +2,7 @@ package Slic3r::Perimeter; use Moo; use Math::Clipper ':all'; -use Slic3r::Geometry qw(X Y shortest_path); +use Slic3r::Geometry qw(X Y shortest_path scale); use XXX; sub make_perimeter { @@ -30,13 +30,13 @@ sub make_perimeter { # organize perimeter surfaces using a shortest path search my @surfaces = @{shortest_path([ - map [ $_->contour->points->[0], $_ ], @{$layer->surfaces}, + map [ $_->contour->points->[0], $_ ], @{$layer->slices}, ])}; foreach my $surface (@surfaces) { # the outer loop must be offsetted by half extrusion width inwards my @last_offsets = ($surface->expolygon); - my $distance = $Slic3r::flow_width / 2 / $Slic3r::resolution; + my $distance = scale $Slic3r::flow_width / 2; # create other offsets push @perimeters, []; @@ -51,7 +51,7 @@ sub make_perimeter { # create one more offset to be used as boundary for fill { - $distance -= $Slic3r::flow_width * $Slic3r::perimeter_infill_overlap_ratio / $Slic3r::resolution; + $distance -= scale $Slic3r::flow_width * $Slic3r::perimeter_infill_overlap_ratio; my @fill_boundaries = map Slic3r::Surface->cast_from_expolygon ($_, surface_type => $surface->surface_type), map $_->offset_ex(-$distance), @last_offsets; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index f6fc17cbd7..927598e8ef 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -2,14 +2,11 @@ package Slic3r::Print; use Moo; use Math::ConvexHull 1.0.4 qw(convex_hull); -use Slic3r::Geometry qw(X Y Z PI scale); +use Slic3r::Geometry qw(X Y Z PI MIN MAX scale); use Slic3r::Geometry::Clipper qw(explode_expolygons safety_offset diff_ex intersection_ex union_ex offset JT_ROUND JT_MITER); use XXX; -use constant MIN => 0; -use constant MAX => 1; - has 'x_length' => ( is => 'ro', required => 1, @@ -184,17 +181,6 @@ sub detect_surfaces_type { @$expolygons; }; - # clip surfaces to the fill boundaries - foreach my $layer (@{$self->layers}) { - my $intersection = intersection_ex( - [ map $_->p, @{$layer->surfaces} ], - [ map $_->p, @{$layer->fill_boundaries} ], - ); - @{$layer->surfaces} = map Slic3r::Surface->cast_from_expolygon - ($_, surface_type => 'internal'), - @$intersection; - } - for (my $i = 0; $i < $self->layer_count; $i++) { my $layer = $self->layers->[$i]; Slic3r::debugf "Detecting solid surfaces for layer %d\n", $layer->id; @@ -206,20 +192,20 @@ sub detect_surfaces_type { # find top surfaces (difference between current surfaces # of current layer and upper one) if ($upper_layer) { - @top = $surface_difference->($layer->surfaces, $upper_layer->surfaces, 'top'); + @top = $surface_difference->($layer->slices, $upper_layer->slices, 'top'); } else { # if no upper layer, all surfaces of this one are solid - @top = @{$layer->surfaces}; + @top = @{$layer->slices}; $_->surface_type('top') for @top; } # find bottom surfaces (difference between current surfaces # of current layer and lower one) if ($lower_layer) { - @bottom = $surface_difference->($layer->surfaces, $lower_layer->surfaces, 'bottom'); + @bottom = $surface_difference->($layer->slices, $lower_layer->slices, 'bottom'); } else { # if no lower layer, all surfaces of this one are solid - @bottom = @{$layer->surfaces}; + @bottom = @{$layer->slices}; $_->surface_type('bottom') for @bottom; } @@ -233,14 +219,28 @@ sub detect_surfaces_type { } # find internal surfaces (difference between top/bottom surfaces and others) - @internal = $surface_difference->($layer->surfaces, [@top, @bottom], 'internal'); + @internal = $surface_difference->($layer->slices, [@top, @bottom], 'internal'); # save surfaces to layer - $layer->surfaces([ @bottom, @top, @internal ]); + $layer->slices([ @bottom, @top, @internal ]); Slic3r::debugf " layer %d has %d bottom, %d top and %d internal surfaces\n", $layer->id, scalar(@bottom), scalar(@top), scalar(@internal); } + + # clip surfaces to the fill boundaries + foreach my $layer (@{$self->layers}) { + @{$layer->surfaces} = (); + foreach my $surface (@{$layer->slices}) { + my $intersection = intersection_ex( + [ $surface->p ], + [ map $_->p, @{$layer->fill_boundaries} ], + ); + push @{$layer->surfaces}, map Slic3r::Surface->cast_from_expolygon + ($_, surface_type => $surface->surface_type), + @$intersection; + } + } } sub discover_horizontal_shells { @@ -327,7 +327,7 @@ sub extrude_skirt { # collect points from all layers contained in skirt height my @points = (); my @layers = map $self->layer($_), 0..($Slic3r::skirt_height-1); - push @points, map @$_, map $_->p, map @{ $_->surfaces }, @layers; + push @points, map @$_, map $_->p, map @{ $_->slices }, @layers; # find out convex hull my $convex_hull = convex_hull(\@points); @@ -335,7 +335,7 @@ sub extrude_skirt { # draw outlines from outside to inside my @skirts = (); for (my $i = $Slic3r::skirts - 1; $i >= 0; $i--) { - my $distance = ($Slic3r::skirt_distance + ($Slic3r::flow_width * $i)) / $Slic3r::resolution; + my $distance = scale ($Slic3r::skirt_distance + ($Slic3r::flow_width * $i)); my $outline = offset([$convex_hull], $distance, $Slic3r::resolution * 100, JT_ROUND); push @skirts, Slic3r::ExtrusionLoop->cast([ @{$outline->[0]} ], role => 'skirt'); } diff --git a/lib/Slic3r/Skein.pm b/lib/Slic3r/Skein.pm index da5b08e74e..d229d7d20e 100644 --- a/lib/Slic3r/Skein.pm +++ b/lib/Slic3r/Skein.pm @@ -45,7 +45,7 @@ sub go { $self->status_cb->(30, "Detecting solid surfaces..."); $print->detect_surfaces_type; - # prepare fill surfaces + # decide what surfaces are to be filled $self->status_cb->(35, "Preparing infill surfaces..."); $_->prepare_fill_surfaces for @{$print->layers}; diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm index abfa8a2478..b1f3ee545a 100644 --- a/lib/Slic3r/Surface.pm +++ b/lib/Slic3r/Surface.pm @@ -60,7 +60,7 @@ sub group { my $type = ($params->{merge_solid} && $surface->surface_type =~ /top|bottom|solid/) ? 'solid' : $surface->surface_type; - $type .= "_" . ($surface->bridge_angle || ''); + $type .= "_" . ($surface->bridge_angle // ''); #/ $type .= "_" . $surface->depth_layers; $unique_types{$type} ||= []; push @{ $unique_types{$type} }, $surface; diff --git a/t/angles.t b/t/angles.t index 551c0a4c26..0caac718db 100644 --- a/t/angles.t +++ b/t/angles.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 23; +plan tests => 24; BEGIN { use FindBin; @@ -50,6 +50,7 @@ use Slic3r::Geometry qw(line_atan line_direction rad2deg_dir PI); 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°'; + is rad2deg_dir(PI/6*2), 30, '60°'; } #==========================================================