diff --git a/README.markdown b/README.markdown index 08b26b5dd..d59ffb5d6 100644 --- a/README.markdown +++ b/README.markdown @@ -34,7 +34,7 @@ layers and representing internally the following features: This kind of abstraction will allow to implement particular logic and allow the user to specify custom options. -I need to implement algorithms to produce perimeter outlines and surface fill. +I need to implement algorithms to produce surface fill, while perimeter is done. Future goals include support material, options to control bridges, skirt, cool. diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 8a6b0e2ab..3e0ff3aea 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -5,6 +5,7 @@ use warnings; use Slic3r::Layer; use Slic3r::Line; +use Slic3r::Perimeter; use Slic3r::Point; use Slic3r::Polyline; use Slic3r::Polyline::Closed; @@ -12,7 +13,9 @@ use Slic3r::Print; use Slic3r::STL; use Slic3r::Surface; -our $layer_height = 0.4; -our $resolution = 0.1; +our $layer_height = 0.4; +our $resolution = 0.1; +our $perimeter_offsets = 3; +our $flow_width = 0.4; # make sure this is > $resolution 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 0cbf07969..733caa4b4 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -32,6 +32,12 @@ has 'surfaces' => ( default => sub { [] }, ); +has 'perimeters' => ( + is => 'rw', + isa => 'ArrayRef[Slic3r::Polyline]', + default => sub { [] }, +); + sub z { my $self = shift; return $self->id * $Slic3r::layer_height / $Slic3r::resolution; @@ -313,7 +319,7 @@ sub merge_contiguous_surfaces { ); printf " merging into new surface %s\n", $new_surface->id; - push @{ $self->surfaces }, $surface; + push @{ $self->surfaces }, $new_surface; $self->remove_surface($_) for ($surface, $neighbor_surface); } diff --git a/lib/Slic3r/Line.pm b/lib/Slic3r/Line.pm index 288a00ac1..92603cfc8 100644 --- a/lib/Slic3r/Line.pm +++ b/lib/Slic3r/Line.pm @@ -1,6 +1,7 @@ package Slic3r::Line; use Moose; +use Moose::Util::TypeConstraints; use Scalar::Util qw(weaken); has 'a' => ( @@ -21,6 +22,11 @@ has 'polyline' => ( weak_ref => 1, ); +has 'solid_side' => ( + is => 'rw', + isa => enum([qw(left right)]), # going from a to b +); + sub BUILD { my $self = shift; @@ -45,6 +51,12 @@ sub coincides_with { || ($self->a->coincides_with($line->b) && $self->b->coincides_with($line->a)); } +sub has_endpoint { + my $self = shift; + my ($point) = @_;#printf " %s has endpoint %s: %s\n", $self->id, $point->id, ($point eq $self->a || $point eq $self->b); + return $point->coincides_with($self->a) || $point->coincides_with($self->b); +} + sub slope { my $self = shift; return undef if $self->b->x == $self->a->x; # line is vertical diff --git a/lib/Slic3r/Perimeter.pm b/lib/Slic3r/Perimeter.pm new file mode 100644 index 000000000..ce148c033 --- /dev/null +++ b/lib/Slic3r/Perimeter.pm @@ -0,0 +1,104 @@ +package Slic3r::Perimeter; +use Moose; + +use Math::Geometry::Planar; +*Math::Geometry::Planar::OffsetPolygon = *Math::Geometry::Planar::Offset::OffsetPolygon; + +sub make_perimeter { + my $self = shift; + my ($layer) = @_; + printf "Making perimeter for layer %d:\n", $layer->id; + + # skip entire section if no perimeters are requested + return unless $Slic3r::perimeter_offsets > 0; + + my (@perimeters, %contours, %holes) = (); + foreach my $surface (@{ $layer->surfaces }) { + $contours{$surface} = []; + $holes{$surface} = []; + + # first perimeter + { + my $polygon = $surface->mgp_polygon; + my ($contour_p, @holes_p) = @{ $polygon->polygons }; + push @{ $contours{$surface} }, $contour_p; + push @{ $holes{$surface} }, @holes_p; + push @perimeters, $polygon; + } + + # create other offsets + for (my $loop = 1; $loop < $Slic3r::perimeter_offsets; $loop++) { + my ($contour_p, @holes_p) = map $self->_mgp_from_points_ref($_), @{ $perimeters[-1]->polygons }; + + # generate offsets + my $contour_offsets = $contour_p->offset_polygon($Slic3r::flow_width / $Slic3r::resolution); + my @hole_offsets = map @$_, map $_->offset_polygon(- $Slic3r::flow_width / $Slic3r::resolution), @holes_p; + + # now we subtract perimeter offsets from the contour offset polygon + # this will generate a single polygon with correct holes and also + # will take care of collisions between contour offset and holes + foreach my $contour_points (@$contour_offsets) { + my $tmp = $self->_mgp_from_points_ref($contour_points)->convert2gpc; + foreach my $hole_points (@hole_offsets) { + $hole_points = $self->_mgp_from_points_ref($hole_points)->convert2gpc; + $tmp = GpcClip('DIFFERENCE', $tmp, $hole_points); + } + + my ($result) = Gpc2Polygons($tmp); + # now we've got $result, which is a Math::Geometry::Planar + # representing the inner surface including hole perimeters + + my $result_polylines = $result->polygons; + + ($contour_p, @holes_p) = @$result_polylines; + push @{ $contours{$surface} }, $contour_p; + push @{ $holes{$surface} }, @holes_p; + push @perimeters, $result; + } + } + } + + # generate paths for holes + # we start from innermost loops (that is, external ones), do them + # for all holes, than go on with inner loop and do that for all + # holes and so on + foreach my $p (map @$_, values %holes) { + push @{ $layer->perimeters }, Slic3r::Polyline->new_from_points(@{ $p->points }); + } + + # generate paths for contours + # this time we do something different: we do contour loops for one + # shape (that is, one original surface) at a time: we start from the + # innermost loop (that is, internal one), then without interrupting + # our path we go onto the outer loop and continue; this should ensure + # good surface quality + foreach my $polylines (values %contours) { + my @path_points = (); + foreach my $p (map $self->_mgp_from_points_ref($_), @$polylines) { + my $points = $p->points; + # TODO: the initial $points->[0] should be replaced by the point of + # the segment which is $Slic3r::flow_width / $Slic3r::resolution + # away from it to avoid the extruder to get two times there + push @path_points, @$points, $points->[0]; + } + push @{ $layer->perimeters }, Slic3r::Polyline->new_from_points(reverse @path_points); + } +} + +sub _mgp_from_points_ref { + my $self = shift; + my ($points) = @_; + my $p = Math::Geometry::Planar->new; + $p->points($points); + return $p; +} + +sub _mgp_from_polygons_ref { + my $self = shift; + my ($polygons) = @_; + my $p = Math::Geometry::Planar->new; + $p->polygons($polygons); + return $p; +} + +1; diff --git a/lib/Slic3r/Point.pm b/lib/Slic3r/Point.pm index 3f981a831..16130048c 100644 --- a/lib/Slic3r/Point.pm +++ b/lib/Slic3r/Point.pm @@ -35,6 +35,8 @@ sub coincides_with { my $self = shift; my ($point) = @_; + $point = Slic3r::Point->new(x => $point->[0], y => $point->[1]) #== + if ref $point eq 'ARRAY'; return $self->x == $point->x && $self->y == $point->y; #= } diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index e1f1d4184..27c1e543c 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -26,7 +26,7 @@ sub BUILD { sub id { my $self = shift; - return join '-', map($_->id, $self->points); + return join '-', map($_->id, $self->ordered_points(1)); } sub new_from_points { @@ -61,4 +61,44 @@ sub points { return values %points; } +sub ordered_points { + my $self = shift; + my ($as_objects) = @_; + my $points = []; + + #printf "\n\n==> Number of lines: %d\n", scalar @{ $self->lines }; + my @lines = @{ $self->lines }; + while (@lines && @$points < @{ $self->lines }) { + #printf "\nNumber of points: %d\n", scalar @{ $points }; + my @temp = @lines; + @lines = (); + foreach my $line (@temp) { + #printf "Line: %s\n", $line->id; + my $point; + if (!@$points) { + # make sure we start from a point not connected to another segment if any + push @$points, sort { @{$a->lines} <=> @{$b->lines} } $line->points; + next; + } elsif ($line->has_endpoint($points->[-1])) { + $point = +(grep $points->[-1] ne $_, $line->points)[0]; + } + if (!$point) { + #printf " no point found, retrying\n"; + push @lines, $line; + next; + } + #printf " adding point %s\n", $point->id; + push @$points, $point; + } + } + + pop @$points + if $self->isa('Slic3r::Polyline::Closed') && $points->[0]->coincides_with($points->[-1]); + + return @$points if $as_objects; + + $points = [ map [ $_->x, $_->y ], @$points ]; #] + return $points; +} + 1; diff --git a/lib/Slic3r/Polyline/Closed.pm b/lib/Slic3r/Polyline/Closed.pm index f542e45bd..222386bd8 100644 --- a/lib/Slic3r/Polyline/Closed.pm +++ b/lib/Slic3r/Polyline/Closed.pm @@ -58,4 +58,15 @@ sub encloses_point { return $side; } +sub mgp_polygon { + my $self = shift; + + # we need a list of ordered points + my $points = $self->ordered_points; + + my $p = Math::Geometry::Planar->new; + $p->points($points); + return $p; +} + 1; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 32003861e..f0c5f270f 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -26,4 +26,16 @@ sub layer { return $self->layers->[$layer_id]; } +sub extrude_perimeters { + my $self = shift; + + my $perimeter_extruder = Slic3r::Perimeter->new; + + foreach my $layer (@{ $self->layers }) { + $perimeter_extruder->make_perimeter($layer); + printf " generated paths: %s\n", + join ' ', map $_->id, @{ $layer->perimeters }; + } +} + 1; diff --git a/lib/Slic3r/Surface.pm b/lib/Slic3r/Surface.pm index 1558b61ee..d8ab0ddc5 100644 --- a/lib/Slic3r/Surface.pm +++ b/lib/Slic3r/Surface.pm @@ -1,6 +1,7 @@ package Slic3r::Surface; use Moose; +use Math::Geometry::Planar; use Moose::Util::TypeConstraints; has 'contour' => ( @@ -55,4 +56,12 @@ sub encloses_point { return 1; } +sub mgp_polygon { + my $self = shift; + + my $p = Math::Geometry::Planar->new; + $p->polygons([ map $_->points, $self->contour->mgp_polygon, map($_->mgp_polygon, @{ $self->holes }) ]); + return $p; +} + 1; diff --git a/slic3r.pl b/slic3r.pl index c295f6fdd..452822115 100644 --- a/slic3r.pl +++ b/slic3r.pl @@ -14,6 +14,8 @@ use XXX; my $stl_parser = Slic3r::STL->new; my $print = $stl_parser->parse_file("testcube20mm.stl"); +$print->extrude_perimeters; + #XXX $print; __END__