From 743f2abcf2e361f8830de560d24d4bb095139aa9 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 7 Oct 2011 19:07:57 +0200 Subject: [PATCH] Detection of optimal infill direction for bridges. Includes many fixes and improvements. --- README.markdown | 6 +- lib/Slic3r.pm | 1 + lib/Slic3r/Fill.pm | 2 + lib/Slic3r/Fill/Base.pm | 8 +- lib/Slic3r/Fill/Rectilinear.pm | 2 +- lib/Slic3r/Fill/Rectilinear2.pm | 2 +- lib/Slic3r/Geometry.pm | 61 +++++++- lib/Slic3r/Layer.pm | 262 +++++++++++++++++++++++++++++--- lib/Slic3r/Line.pm | 5 + lib/Slic3r/Perimeter.pm | 31 ++-- lib/Slic3r/Polyline/Closed.pm | 15 ++ lib/Slic3r/Print.pm | 28 +++- lib/Slic3r/STL.pm | 8 +- lib/Slic3r/SVG.pm | 6 +- lib/Slic3r/Skein.pm | 37 ++++- lib/Slic3r/Surface.pm | 23 +++ lib/Slic3r/Surface/Bridge.pm | 8 + t/geometry.t | 8 +- 18 files changed, 445 insertions(+), 68 deletions(-) create mode 100644 lib/Slic3r/Surface/Bridge.pm diff --git a/README.markdown b/README.markdown index a37864bf0..8bcc69e27 100644 --- a/README.markdown +++ b/README.markdown @@ -41,6 +41,8 @@ Slic3r current features are: * skirt (with rounded corners); * use relative or absolute extrusion commands; * high-res perimeters (like the "Skin" plugin for Skeinforge); +* detect optimal infill direction for bridges; +* save configuration profiles; * center print around bed center point; * multiple solid layers near horizontal external surfaces; * ability to scale, rotate and multiply input object; @@ -51,10 +53,8 @@ Roadmap includes the following goals: * output some statistics; * allow the user to customize initial and final GCODE commands; * support material for internal perimeters; -* detect optimal infill direction for bridges; * cool; -* other fill patterns; -* nice packaging for cross-platform deployment. +* other fill patterns. ## Is it usable already? diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 3178162bf..e6e2e64e3 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -26,6 +26,7 @@ use Slic3r::Print; use Slic3r::Skein; use Slic3r::STL; use Slic3r::Surface; +use Slic3r::Surface::Bridge; use Slic3r::Surface::Collection; # printer options diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index f922d7063..dfdc2d1a4 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -5,6 +5,8 @@ use Slic3r::Fill::Base; use Slic3r::Fill::Rectilinear; use Slic3r::Fill::Rectilinear2; +use XXX; + has 'print' => (is => 'ro', required => 1); has 'fillers' => (is => 'rw', default => sub { {} }); diff --git a/lib/Slic3r/Fill/Base.pm b/lib/Slic3r/Fill/Base.pm index 0e685b94a..da71d4e57 100644 --- a/lib/Slic3r/Fill/Base.pm +++ b/lib/Slic3r/Fill/Base.pm @@ -10,7 +10,7 @@ use constant PI => 4 * atan2(1, 1); sub infill_direction { my $self = shift; - my ($polygons) = @_; + my ($surface) = @_; # set infill angle my (@rotate, @shift); @@ -23,7 +23,11 @@ sub infill_direction { $rotate[0] = Slic3r::Geometry::deg2rad($Slic3r::fill_angle) + PI/2; } - # TODO: here we should implement an "infill in direction of bridges" option + # use bridge angle + if ($surface->isa('Slic3r::Surface::Bridge')) { + Slic3r::debugf "Filling bridge with angle %d\n", $surface->bridge_angle; + $rotate[0] = Slic3r::Geometry::deg2rad($surface->bridge_angle); + } @shift = @{ +(Slic3r::Geometry::rotate_points(@rotate, \@shift))[0] }; return [\@rotate, \@shift]; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index e834b866c..ff8b7bba4 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -16,7 +16,7 @@ sub fill_surface { # rotate polygons so that we can work with vertical lines here my $polygons = [ $surface->p ]; - my $rotate_vector = $self->infill_direction($polygons); + my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($polygons, $rotate_vector); my $bounding_box = [ Slic3r::Geometry::bounding_box(map @$_, $polygons) ]; diff --git a/lib/Slic3r/Fill/Rectilinear2.pm b/lib/Slic3r/Fill/Rectilinear2.pm index e96c61fa0..aee115a72 100644 --- a/lib/Slic3r/Fill/Rectilinear2.pm +++ b/lib/Slic3r/Fill/Rectilinear2.pm @@ -21,7 +21,7 @@ sub fill_surface { my $polygons = [ $surface->p ]; # rotate polygons so that we can work with vertical lines here - my $rotate_vector = $self->infill_direction($polygons); + my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($polygons, $rotate_vector); my $distance_between_lines = $Slic3r::flow_width / $Slic3r::resolution / $params{density}; diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index fb6540990..54abea08e 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -10,9 +10,11 @@ use constant A => 0; use constant B => 1; use constant X => 0; use constant Y => 1; -use constant epsilon => 1E-4; our $parallel_degrees_limit = abs(deg2rad(3)); +our $epsilon = 1E-4; +sub epsilon () { $epsilon } + sub slope { my ($line) = @_; return undef if abs($line->[B][X] - $line->[A][X]) < epsilon; # line is vertical @@ -60,6 +62,16 @@ sub distance_between_points { return sqrt((($p1->[X] - $p2->[X])**2) + ($p1->[Y] - $p2->[Y])**2); } +sub line_length { + my ($line) = @_; + return distance_between_points(@$line[A, B]); +} + +sub midpoint { + my ($line) = @_; + return [ ($line->[B][X] + $line->[A][X]) / 2, ($line->[B][Y] + $line->[A][Y]) / 2 ]; +} + sub point_in_polygon { my ($point, $polygon) = @_; @@ -118,6 +130,13 @@ sub point_in_segment { return abs($y3 - $y) < epsilon ? 1 : 0; } +sub segment_in_segment { + my ($needle, $haystack) = @_; + + # a segment is contained in another segment if its endpoints are contained + return point_in_segment($needle->[A], $haystack) && point_in_segment($needle->[B], $haystack); +} + sub point_is_on_left_of_segment { my ($point, $line) = @_; @@ -125,19 +144,24 @@ sub point_is_on_left_of_segment { - ($line->[B][Y] - $line->[A][Y])*($point->[X] - $line->[A][X])) > 0; } -sub polygon_lines { +sub polyline_lines { my ($polygon) = @_; my @lines = (); - my $last_point = $polygon->[-1]; + my $last_point; foreach my $point (@$polygon) { - push @lines, [ $last_point, $point ]; + push @lines, [ $last_point, $point ] if $last_point; $last_point = $point; } return @lines; } +sub polygon_lines { + my ($polygon) = @_; + return polyline_lines([ @$polygon, $polygon->[0] ]); +} + sub nearest_point { my ($point, $points) = @_; @@ -179,6 +203,30 @@ sub polygon_segment_having_point { return undef; } +# return true if the given segment is contained in any edge of the polygon +sub polygon_has_subsegment { + my ($polygon, $segment) = @_; + foreach my $line (polygon_lines($polygon)) { + return 1 if segment_in_segment($segment, $line); + } + return 0; +} + +sub polygon_has_vertex { + my ($polygon, $point) = @_; + foreach my $p (@$polygon) { + return 1 if points_coincide($p, $point); + } + return 0; +} + +sub polyline_length { + my ($polyline) = @_; + my $length = 0; + $length += line_length($_) for polygon_lines($polyline); + return $length; +} + sub can_connect_points { my ($p1, $p2, $polygons) = @_; @@ -207,6 +255,11 @@ sub deg2rad { return PI() * $degrees / 180; } +sub rad2deg { + my ($rad) = @_; + return $rad / PI() * 180; +} + sub rotate_points { my ($radians, $center, @points) = @_; $center ||= [0,0]; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 2f25706d3..448927142 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -2,8 +2,11 @@ package Slic3r::Layer; use Moo; use Math::Clipper ':all'; +use Math::ConvexHull qw(convex_hull); use XXX; +use constant PI => 4 * atan2(1, 1); + # a sequential number of layer, starting at 0 has 'id' => ( is => 'ro', @@ -26,17 +29,31 @@ has 'surfaces' => ( default => sub { [] }, ); +# collection of surfaces representing bridges +has 'bridges' => ( + is => 'rw', + #isa => 'ArrayRef[Slic3r::Surface::Bridge]', + default => sub { [] }, +); + +# collection of surfaces to make perimeters for +has 'perimeter_surfaces' => ( + is => 'rw', + #isa => 'ArrayRef[Slic3r::Surface]', + default => sub { [] }, +); + # ordered collection of extrusion paths to build all perimeters has 'perimeters' => ( is => 'rw', - #isa => 'ArrayRef[Slic3r::ExtrusionPath]', + #isa => 'ArrayRef[Slic3r::ExtrusionLoop]', default => sub { [] }, ); # ordered collection of extrusion paths to build skirt loops has 'skirts' => ( is => 'rw', - #isa => 'ArrayRef[Slic3r::ExtrusionPath]', + #isa => 'ArrayRef[Slic3r::ExtrusionLoop]', default => sub { [] }, ); @@ -44,7 +61,7 @@ has 'skirts' => ( # they represent boundaries of areas to fill has 'fill_surfaces' => ( is => 'rw', - #isa => 'ArrayRef[Slic3r::Surface]', + #isa => 'ArrayRef[Slic3r::Surface::Collection]', default => sub { [] }, ); @@ -101,18 +118,34 @@ sub remove_surface { } # build polylines of lines which do not already belong to a surface +# okay, this code is a mess. will need some refactoring. sorry. sub make_polylines { my $self = shift; # remove line duplicates - { + if (0) { + # this removes any couple of coinciding Slic3r::Line::FacetEdge + my %lines_map = (); + foreach my $line (grep $_->isa('Slic3r::Line::FacetEdge'), @{ $self->lines }) { + my $ordered_id = $line->ordered_id; + if (exists $lines_map{$ordered_id}) { + delete $lines_map{$ordered_id}; + next; + } + $lines_map{$ordered_id} = $line; + } + + @{ $self->lines } = (values(%lines_map), grep !$_->isa('Slic3r::Line::FacetEdge'), @{ $self->lines }); + } + if (1) { + # this removes any duplicate, leaving one my %lines_map = map { join(',', sort map $_->id, @{$_->points} ) => "$_" } @{ $self->lines }; %lines_map = reverse %lines_map; @{ $self->lines } = grep $lines_map{"$_"}, @{ $self->lines }; } # now remove lines that are already part of a surface - { + if (1) { my @lines = @{ $self->lines }; @{ $self->lines } = (); LINE: foreach my $line (@lines) { @@ -130,26 +163,31 @@ sub make_polylines { } # make a cache of line endpoints - my %pointmap = (); + my (%pointmap) = (); foreach my $line (@{ $self->lines }) { for my $point (@{ $line->points }) { $pointmap{$point->id} ||= []; push @{ $pointmap{$point->id} }, $line; } } - - # defensive programming - #die "No point should be endpoint of less or more than 2 lines!" - # if grep @$_ != 2, values %pointmap; + foreach my $point_id (keys %pointmap) { + $pointmap{$point_id} = [ + sort { $a->isa('Slic3r::Line::FacetEdge') <=> $b->isa('Slic3r::Line::FacetEdge') } + @{$pointmap{$point_id}} ]; + } if (0) { # defensive programming for (keys %pointmap) { next if @{$pointmap{$_}} == 2; - #use Slic3r::SVG; - #Slic3r::SVG::output_points($main::print, "points.svg", [ map [split /,/], keys %pointmap ], [ [split /,/, $_ ] ]); - #Slic3r::SVG::output_lines($main::print, "lines.svg", [ map $_->p, @{$self->lines} ]); + use Slic3r::SVG; + Slic3r::SVG::output(undef, "lines_and_points.svg", + lines => [ map $_->p, grep !$_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ], + red_lines => [ map $_->p, grep $_->isa('Slic3r::Line::FacetEdge'), @{$self->lines} ], + points => [ map [split /,/], keys %pointmap ], + red_points => [ [split /,/, $_ ] ], + ); YYY $pointmap{$_}; @@ -198,6 +236,11 @@ sub make_polylines { # remove last point as it coincides with first one pop @$points; + if (@$points == 1 && $first_line->isa('Slic3r::Line::FacetEdge')) { + Slic3r::debugf "Skipping spare facet edge"; + next; + } + die sprintf "Invalid polyline with only %d points\n", scalar(@$points) if @$points < 3; Slic3r::debugf "Discovered polyline of %d points (%s)\n", scalar @$points, @@ -336,6 +379,21 @@ sub merge_contiguous_surfaces { $resulting_surfaces{$type} = $result2; } + # remove overlapping surfaces + # (remove anything that is not internal from areas covered by internal surfaces) + # this may happen because of rounding of Z coordinates: the model could have + # features smaller than our layer height, so we'd get more things on a single + # layer + if (0) { # not proven to be necessary until now + my $clipper = Math::Clipper->new; + foreach my $type (qw(bottom top)) { + $clipper->clear; + $clipper->add_subject_polygons([ map { $_->{outer}, @{$_->{holes}} } @{$resulting_surfaces{$type}} ]); + $clipper->add_clip_polygons([ map { $_->{outer}, @{$_->{holes}} } @{$resulting_surfaces{internal}} ]); + $resulting_surfaces{$type} = $clipper->ex_execute(CT_DIFFERENCE, PFT_NONZERO, PFT_NONZERO); + } + } + # save surfaces @{ $self->surfaces } = (); foreach my $type (keys %resulting_surfaces) { @@ -357,23 +415,181 @@ sub merge_contiguous_surfaces { } } -sub remove_small_features { +sub remove_small_surfaces { my $self = shift; + my @good_surfaces = (); - # for each perimeter, try to get an inwards offset - # for a distance equal to half of the extrusion width; - # if no offset is possible, then feature is not printable - my @good_perimeters = (); - foreach my $loop (@{$self->perimeters}) { - my $p = $loop->p; - @$p = reverse @$p if !is_counter_clockwise($p); - my $offsets = offset([$p], -($Slic3r::flow_width / 2 / $Slic3r::resolution), $Slic3r::resolution * 100000, JT_MITER, 2); - push @good_perimeters, $loop if @$offsets; + foreach my $surface (@{$self->surfaces}) { + next if !$surface->contour->is_printable; + @{$surface->holes} = grep $_->is_printable, @{$surface->holes}; + push @good_surfaces, $surface; } + + @{$self->surfaces} = @good_surfaces; +} + +sub remove_small_perimeters { + my $self = shift; + my @good_perimeters = grep $_->is_printable, @{$self->perimeters}; Slic3r::debugf "removed %d unprintable perimeters\n", (@{$self->perimeters} - @good_perimeters) if @good_perimeters != @{$self->perimeters}; @{$self->perimeters} = @good_perimeters; } +# make bridges printable +sub process_bridges { + my $self = shift; + return if $self->id == 0; + + # a bottom surface on a layer > 0 is either a bridge or a overhang + # or a combination of both + + my @bottom_surfaces = grep $_->surface_type eq 'bottom', @{$self->surfaces} or return; + my @supporting_surfaces = grep $_->surface_type =~ /internal/, @{$self->surfaces}; + + SURFACE: foreach my $surface (@bottom_surfaces) { + # since we can't print concave bridges, we transform the surface + # in a convex polygon; this will print thin membranes eventually + my $surface_p = convex_hull($surface->contour->p); + + # find all supported edges (as polylines, thus keeping notion of + # consecutive supported edges) + my @supported_polylines = (); + { + my @current_polyline = (); + EDGE: foreach my $edge (Slic3r::Geometry::polygon_lines($surface_p)) { + for (@supporting_surfaces) { + local $Slic3r::Geometry::epsilon = 1E+7; + if (Slic3r::Geometry::polygon_has_subsegment($_->contour->p, $edge)) { + push @current_polyline, $edge; + next EDGE; + } + } + if (@current_polyline) { + push @supported_polylines, [@current_polyline]; + @current_polyline = (); + } + } + push @supported_polylines, [@current_polyline] if @current_polyline; + } + + # defensive programming, this shouldn't happen + if (@supported_polylines == 0) { + Slic3r::debugf "Found bridge/overhang with no supports on layer %d; ignoring\n", $self->id; + next SURFACE; + } + + if (@supported_polylines == 1) { + Slic3r::debugf "Found bridge/overhang with only one support on layer %d; ignoring\n", $self->id; + next SURFACE; + } + + # now connect the first point to the last of each polyline + @supported_polylines = map [ $_->[0]->[0], $_->[-1]->[-1] ], @supported_polylines; + + # if we got more than two supports, get the longest two + if (@supported_polylines > 2) { + my %lengths = map { "$_" => Slic3r::Geometry::line_length($_) }, @supported_polylines; + @supported_polylines = sort { $lengths{"$a"} <=> $lengths{"$b"} } @supported_polylines; + @supported_polylines = @supported_polylines[0,1]; + } + + # connect the midpoints, that will give the the optimal infill direction + my @midpoints = map Slic3r::Geometry::midpoint($_), @supported_polylines; + my $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; + + # detect which neighbor surfaces are now supporting our bridge + my @supporting_neighbor_surfaces = (); + foreach my $supporting_surface (@supporting_surfaces) { + local $Slic3r::Geometry::epsilon = 1E+7; + push @supporting_neighbor_surfaces, $supporting_surface + if grep Slic3r::Geometry::polygon_has_vertex($supporting_surface->contour->p, $_), + map $_->[0], @supported_polylines; + } + + # defensive programming, this shouldn't happen + if (@supporting_neighbor_surfaces == 0) { + Slic3r::debugf "Couldn't find supporting surfaces on layer %d; ignoring\n", $self->id; + next SURFACE; + } + + # now, extend our bridge by taking a portion of supporting surfaces + { + # offset the bridge by 5mm + my $bridge_offset = ${ offset([$surface_p], 5 / $Slic3r::resolution, $Slic3r::resolution * 100, JT_MITER, 2) }[0]; + + # calculate the new bridge + my $clipper = Math::Clipper->new; + $clipper->add_subject_polygon($surface_p); + $clipper->add_subject_polygons([ map $_->p, @supporting_neighbor_surfaces ]); + $clipper->add_clip_polygon($bridge_offset); + my $intersection = $clipper->execute(CT_INTERSECTION, PFT_NONZERO, PFT_NONZERO); + + push @{$self->bridges}, map Slic3r::Surface::Bridge->cast_from_polygon($_, + surface_type => 'bottom', + bridge_angle => $bridge_angle, + ), @$intersection; + } + } +} + +# generates a set of surfaces that will be used to make perimeters +# thus, we need to merge internal surfaces and bridges +sub detect_perimeter_surfaces { + my $self = shift; + + # little optimization: skip the Clipper UNION if we have no bridges + if (!@{$self->bridges}) { + push @{$self->perimeter_surfaces}, @{$self->surfaces}; + } else { + my $clipper = Math::Clipper->new; + $clipper->add_subject_polygons([ map $_->p, grep $_->surface_type =~ /internal/, @{$self->surfaces} ]); + $clipper->add_clip_polygons([ map $_->p, @{$self->bridges} ]); + my $union = $clipper->ex_execute(CT_UNION, PFT_NONZERO, PFT_NONZERO); + + push @{$self->perimeter_surfaces}, + map Slic3r::Surface->cast_from_expolygon($_, surface_type => 'internal'), + @$union; + + push @{$self->perimeter_surfaces}, + grep $_->surface_type !~ /internal/ && ($_->surface_type ne 'bottom' || $self->id == 0), + @{$self->surfaces}; + } +} + +# splits fill_surfaces in internal and bridge surfaces +sub split_bridges_fills { + my $self = shift; + + my $clipper = Math::Clipper->new; + foreach my $surf_coll (@{$self->fill_surfaces}) { + my @surfaces = @{$surf_coll->surfaces}; + @{$surf_coll->surfaces} = (); + + # intersect fill_surfaces with bridges to get actual bridges + foreach my $bridge (@{$self->bridges}) { + $clipper->clear; + $clipper->add_subject_polygons([ map $_->p, @surfaces ]); + $clipper->add_clip_polygon($bridge->contour->p); + my $intersection = $clipper->ex_execute(CT_INTERSECTION, PFT_NONZERO, PFT_NONZERO); + push @{$surf_coll->surfaces}, map Slic3r::Surface::Bridge->cast_from_expolygon($_, + surface_type => 'bottom', + bridge_angle => $bridge->bridge_angle, + ), @$intersection; + } + + # difference between fill_surfaces and bridges are the other surfaces + foreach my $surface (@surfaces) { + $clipper->clear; + $clipper->add_subject_polygons([ $surface->p ]); + $clipper->add_clip_polygons([ map $_->contour->p, @{$self->bridges} ]); + my $difference = $clipper->ex_execute(CT_DIFFERENCE, PFT_NONZERO, PFT_NONZERO); + push @{$surf_coll->surfaces}, map Slic3r::Surface->cast_from_expolygon($_, + surface_type => $surface->surface_type), @$difference; + } + } +} + 1; diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 5d7a4a5be..cbfccb4c6 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -27,6 +27,11 @@ sub id { return $self->a->id . "-" . $self->b->id; } +sub ordered_id { + my $self = shift; + return join('-', sort map $_->id, @{$self->points}); +} + sub coordinates { my $self = shift; return ($self->a->coordinates, $self->b->coordinates); diff --git a/lib/Slic3r/Perimeter.pm b/lib/Slic3r/Perimeter.pm index e50a1aea8..9946f1435 100644 --- a/lib/Slic3r/Perimeter.pm +++ b/lib/Slic3r/Perimeter.pm @@ -18,7 +18,7 @@ sub make_perimeter { if $Slic3r::perimeter_offsets == 0; my (%contours, %holes) = (); - foreach my $surface (@{ $layer->surfaces }) { + foreach my $surface (@{ $layer->perimeter_surfaces }) { $contours{$surface} = []; $holes{$surface} = []; my @last_offsets = (); @@ -47,17 +47,16 @@ sub make_perimeter { } # create one more offset to be used as boundary for fill - push @{ $layer->fill_surfaces }, Slic3r::Surface::Collection->new( - surfaces => [ - map Slic3r::Surface->new( - surface_type => $surface->surface_type, - contour => Slic3r::Polyline::Closed->cast($_->{outer}), - holes => [ - map Slic3r::Polyline::Closed->cast($_), @{$_->{holes}} - ], - ), map $self->offset_polygon($_), @last_offsets - ], - ); + { + my @fill_surfaces = map Slic3r::Surface->cast_from_expolygon( + $_, + surface_type => $surface->surface_type, + ), map $self->offset_polygon($_), @last_offsets; + + push @{ $layer->fill_surfaces }, Slic3r::Surface::Collection->new( + surfaces => [@fill_surfaces], + ) if @fill_surfaces; + } } # generate paths for holes: @@ -91,15 +90,13 @@ sub make_perimeter { sub offset_polygon { my $self = shift; my ($polygon) = @_; - - my $distance = $Slic3r::flow_width / $Slic3r::resolution; - # $polygon holds a Math::Clipper ExPolygon hashref representing # a polygon and its holes - my ($contour_p, @holes_p) = ($polygon->{outer}, @{$polygon->{holes}}); # generate offsets - my $offsets = offset([ $contour_p, @holes_p ], -$distance, $Slic3r::resolution * 100000, JT_MITER, 2); + my $distance = $Slic3r::flow_width / $Slic3r::resolution; + my $offsets = offset([ $polygon->{outer}, @{$polygon->{holes}} ], -$distance, + $Slic3r::resolution * 100000, JT_MITER, 2); # defensive programming my (@contour_offsets, @hole_offsets) = (); diff --git a/lib/Slic3r/Polyline/Closed.pm b/lib/Slic3r/Polyline/Closed.pm index 95fd11ee0..5ad6d97ca 100644 --- a/lib/Slic3r/Polyline/Closed.pm +++ b/lib/Slic3r/Polyline/Closed.pm @@ -3,6 +3,8 @@ use Moo; extends 'Slic3r::Polyline'; +use Math::Clipper qw(JT_MITER); + sub lines { my $self = shift; my @lines = $self->SUPER::lines(@_); @@ -32,4 +34,17 @@ sub encloses_point { return Slic3r::Geometry::point_in_polygon($point->p, $self->p); } +# returns false if the polyline is too tight to be printed +sub is_printable { + my $self = shift; + + # try to get an inwards offset + # for a distance equal to half of the extrusion width; + # if no offset is possible, then polyline is not printable + my $p = $self->p; + @$p = reverse @$p if !Math::Clipper::is_counter_clockwise($p); + my $offsets = Math::Clipper::offset([$p], -($Slic3r::flow_width / 2 / $Slic3r::resolution), $Slic3r::resolution * 100000, JT_MITER, 2); + return @$offsets ? 1 : 0; +} + 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 8fe42357e..eaa2641bf 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -35,7 +35,7 @@ sub new_from_stl { print "\n==> PROCESSING SLICES:\n"; foreach my $layer (@{ $print->layers }) { printf "\nProcessing layer %d:\n", $layer->id; - + # build polylines of lines which do not already belong to a surface my $polylines = $layer->make_polylines; @@ -147,11 +147,22 @@ sub discover_horizontal_shells { } } -# remove perimeters and fill surfaces which are too small to be extruded -sub remove_small_features { +# remove surfaces which are too small to be extruded +sub remove_small_surfaces { my $self = shift; - - $_->remove_small_features for @{$self->layers}; + $_->remove_small_surfaces for @{$self->layers}; +} + +# remove perimeters which are too small to be extruded +sub remove_small_perimeters { + my $self = shift; + $_->remove_small_perimeters for @{$self->layers}; +} + +# make bridges printable +sub process_bridges { + my $self = shift; + $_->process_bridges for @{ $self->layers }; } sub extrude_perimeters { @@ -160,12 +171,19 @@ sub extrude_perimeters { my $perimeter_extruder = Slic3r::Perimeter->new; foreach my $layer (@{ $self->layers }) { + $layer->detect_perimeter_surfaces; $perimeter_extruder->make_perimeter($layer); Slic3r::debugf " generated paths: %s\n", join ' ', map $_->id, @{ $layer->perimeters } if $Slic3r::debug; } } +# splits fill_surfaces in internal and bridge surfaces +sub split_bridges_fills { + my $self = shift; + $_->split_bridges_fills for @{$self->layers}; +} + sub extrude_fills { my $self = shift; diff --git a/lib/Slic3r/STL.pm b/lib/Slic3r/STL.pm index 5407f6054..f142f65cb 100644 --- a/lib/Slic3r/STL.pm +++ b/lib/Slic3r/STL.pm @@ -80,8 +80,10 @@ sub parse_file { $vertex->[$_] = ($Slic3r::scale * $vertex->[$_] / $Slic3r::resolution) + $shift[$_] for X,Y,Z; - # round Z coordinates; XY will be rounded automatically with coercion - $vertex->[Z] = sprintf('%.0f', $vertex->[Z]); + # round Z coordinates to the nearest multiple of layer height + # XY will be rounded automatically to integers with coercion + $vertex->[Z] = sprintf('%.0f', $vertex->[Z] * $Slic3r::resolution / $Slic3r::layer_height) + * $Slic3r::layer_height / $Slic3r::resolution; } foreach my $copy (@copies) { @@ -92,7 +94,7 @@ sub parse_file { $self->_facet($print, $normal, @copy_vertices); } } - + return $print; } diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index 0cc53ca2f..3c743f90c 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -22,14 +22,14 @@ sub output { my $svg = svg($print); - foreach my $type (qw(polygons polylines white_polygons red_polylines)) { + foreach my $type (qw(polygons polylines white_polygons red_polygons red_polylines)) { if ($things{$type}) { my $method = $type =~ /polygons/ ? 'polygon' : 'polyline'; my $g = $svg->group( style => { 'stroke-width' => 2, 'stroke' => $type =~ /red_/ ? 'red' : 'black', - 'fill' => $type eq 'polygons' ? 'grey' : 'none', + 'fill' => ($type !~ /polygons/ ? 'none' : ($type =~ /red_/ ? 'red' : 'grey')), }, ); foreach my $polygon (@{$things{$type}}) { @@ -51,7 +51,7 @@ sub output { my $g = $svg->group( style => { 'stroke-width' => 2, - 'stroke' => 'black', + 'stroke' => $colour, 'fill' => $colour, }, ); diff --git a/lib/Slic3r/Skein.pm b/lib/Slic3r/Skein.pm index b51054082..244fdaf3a 100644 --- a/lib/Slic3r/Skein.pm +++ b/lib/Slic3r/Skein.pm @@ -14,16 +14,40 @@ sub go { if $self->input_file !~ /\.stl$/i; my $t0 = [gettimeofday]; - my $print = Slic3r::Print->new_from_stl($self->input_file); - $print->extrude_perimeters; - $print->remove_small_features; - # detect which surfaces are near external layers + # skein the STL into layers + # each layer has surfaces with holes; surfaces are distinguished + # in top/bottom/internal + my $print = Slic3r::Print->new_from_stl($self->input_file); + + # this will remove unprintable surfaces + # (those that are too tight for extrusion) + $print->remove_small_surfaces; + + # make bridges printable + # this will add a set of bridges to each layer + $print->process_bridges; + + # make perimeters + # this will add a set of extrusion loops to each layer + # as well as a set of surfaces to be filled + $print->extrude_perimeters; + + # this will remove unprintable perimeter loops + # (those that are too tight for extrusion) + $print->remove_small_perimeters; + + # split fill_surfaces in internal and bridge surfaces + $print->split_bridges_fills; + + # detect which fill surfaces are near external layers + # they will be split in internal and internal-solid surfaces $print->discover_horizontal_shells; + # this will generate extrusion paths for each layer $print->extrude_fills; - + # output everything to a GCODE file if (!$self->output_file) { my $output_file = $self->input_file; $output_file =~ s/\.stl$/.gcode/i; @@ -31,9 +55,12 @@ sub go { } $print->export_gcode($self->output_file); + # output some statistics my $processing_time = tv_interval($t0); printf "Done. Process took %d minutes and %.3f seconds\n", int($processing_time/60), $processing_time - int($processing_time/60)*60; + + # TODO: more statistics! } 1; diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm index a6f4355a0..8d94c7c6c 100644 --- a/lib/Slic3r/Surface.pm +++ b/lib/Slic3r/Surface.pm @@ -19,6 +19,29 @@ has 'surface_type' => ( #isa => enum([qw(internal internal-solid bottom top)]), ); +sub cast_from_polygon { + my $class = shift; + my ($polygon, %args) = @_; + + return $class->new( + contour => Slic3r::Polyline::Closed->cast($polygon), + %args, + ); +} + +sub cast_from_expolygon { + my $class = shift; + my ($expolygon, %args) = @_; + + return $class->new( + contour => Slic3r::Polyline::Closed->cast($expolygon->{outer}), + holes => [ + map Slic3r::Polyline::Closed->cast($_), @{$expolygon->{holes}} + ], + %args, + ); +} + sub add_hole { my $self = shift; my ($hole) = @_; diff --git a/lib/Slic3r/Surface/Bridge.pm b/lib/Slic3r/Surface/Bridge.pm new file mode 100644 index 000000000..aba5a8652 --- /dev/null +++ b/lib/Slic3r/Surface/Bridge.pm @@ -0,0 +1,8 @@ +package Slic3r::Surface::Bridge; +use Moo; + +extends "Slic3r::Surface"; + +has 'bridge_angle' => (is => 'ro'); + +1; diff --git a/t/geometry.t b/t/geometry.t index decdad899..fce157ed8 100644 --- a/t/geometry.t +++ b/t/geometry.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 5; +plan tests => 6; BEGIN { use FindBin; @@ -49,6 +49,12 @@ is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; #========================================================== +$point = [ 736310778.185108, 5017423926.8924 ]; +my $line = [ [627484000, 3695776000], [750000000, 3720147000] ]; +is Slic3r::Geometry::point_in_segment($point, $line), 0, 'point_in_segment'; + +#========================================================== + my $polygons = [ [ # contour, ccw [459190000, 5152739000], [147261000, 4612464000], [147261000, 3487535000], [339887000, 3153898000],