From 794b7a99d2ac4ffd1d204bf08a5a31a2121d8db7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 4 Oct 2011 17:55:55 +0200 Subject: [PATCH] Fixes for hi-res STL models --- lib/Slic3r.pm | 2 +- lib/Slic3r/Extruder.pm | 7 +-- lib/Slic3r/Geometry.pm | 11 +++- lib/Slic3r/Layer.pm | 25 ++++++--- lib/Slic3r/Perimeter.pm | 6 +-- lib/Slic3r/STL.pm | 112 +++++++++++++++++++++++++--------------- lib/Slic3r/SVG.pm | 2 +- t/stl.t | 37 +++++++++++++ 8 files changed, 139 insertions(+), 63 deletions(-) create mode 100644 t/stl.t diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index 1273c2f52..823f33fb2 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -44,7 +44,7 @@ our $perimeter_feed_rate = 30; # mm/sec our $bottom_layer_speed_ratio = 0.3; # accuracy options -our $resolution = 0.001; +our $resolution = 0.00000001; our $layer_height = 0.4; our $thickness_ratio = 1; our $flow_width; diff --git a/lib/Slic3r/Extruder.pm b/lib/Slic3r/Extruder.pm index 04f4d50a7..b155d6e6e 100644 --- a/lib/Slic3r/Extruder.pm +++ b/lib/Slic3r/Extruder.pm @@ -8,6 +8,7 @@ has 'z' => (is => 'rw', default => sub {0} ); has 'extrusion_distance' => (is => 'rw', default => sub {0} ); has 'retracted' => (is => 'rw', default => sub {1} ); # this spits out some plastic at start has 'last_pos' => (is => 'rw', default => sub { [0,0] } ); +has 'dec' => (is => 'ro', default => sub { 3 } ); # calculate speeds has 'travel_feed_rate' => ( @@ -27,12 +28,6 @@ has 'retract_speed' => ( default => sub { $Slic3r::retract_speed * 60 }, # mm/min ); -# calculate number of decimals -has 'dec' => ( - is => 'ro', - default => sub { length((1 / $Slic3r::resolution) - 1) + 1 }, -); - use XXX; use constant PI => 4 * atan2(1, 1); diff --git a/lib/Slic3r/Geometry.pm b/lib/Slic3r/Geometry.pm index 918422c78..ce827be9d 100644 --- a/lib/Slic3r/Geometry.pm +++ b/lib/Slic3r/Geometry.pm @@ -10,7 +10,7 @@ use constant A => 0; use constant B => 1; use constant X => 0; use constant Y => 1; -use constant epsilon => 1E-8; +use constant epsilon => 1E-6; use constant epsilon2 => epsilon**2; our $parallel_degrees_limit = abs(deg2rad(3)); @@ -170,4 +170,13 @@ sub move_points { return map [ $shift->[X] + $_->[X], $shift->[Y] + $_->[Y] ], @points; } +# preserves order +sub remove_coinciding_points { + my ($points) = @_; + + my %p = map { sprintf('%f,%f', @$_) => "$_" } @$points; + %p = reverse %p; + @$points = grep $p{"$_"}, @$points; +} + 1; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index f67921341..30cfdd182 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -81,10 +81,9 @@ sub add_surface { sub add_line { my $self = shift; - my ($a, $b) = @_; + my ($line) = @_; - # we accept either a Line object or a couple of points - my $line = Slic3r::Line->cast([ $a, $b ]); + $line = Slic3r::Line->cast($line); push @{ $self->lines }, $line; return $line; @@ -106,6 +105,13 @@ sub remove_surface { sub make_polylines { my $self = shift; + # remove line duplicates + { + my %lines_map = map { join(',', sort map $_->id, @{$_->points} ) => "$_" } @{ $self->lines }; + %lines_map = reverse %lines_map; + @{ $self->lines } = grep $lines_map{"$_"}, @{ $self->lines }; + } + # make a cache of line endpoints my %pointmap = (); foreach my $line (@{ $self->lines }) { @@ -116,8 +122,8 @@ sub make_polylines { } # defensive programming - die "No point should be endpoint of less or more than 2 lines!" - if grep @$_ != 2, values %pointmap; + #die "No point should be endpoint of less or more than 2 lines!" + # if grep @$_ != 2, values %pointmap; if (0) { # defensive programming @@ -126,8 +132,11 @@ sub make_polylines { #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} ]); - die sprintf "No point should be endpoint of less or more than 2 lines (%d)!", scalar(@{$pointmap{$_}}); + YYY $pointmap{$_}; + + die sprintf "No point should be endpoint of less or more than 2 lines ($_ => %d)!", scalar(@{$pointmap{$_}}); } while (my @single_line_points = grep @{$pointmap{$_}} == 1, keys %pointmap) { @@ -172,7 +181,7 @@ sub make_polylines { # remove last point as it coincides with first one pop @$points; - die "Invalid polyline with only 2 points\n" if @$points == 2; + 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, join ' - ', map $_->id, @$points; @@ -341,7 +350,7 @@ sub remove_small_features { 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), 100, JT_MITER, 2); + my $offsets = offset([$p], -($Slic3r::flow_width / 2 / $Slic3r::resolution), $Slic3r::resolution * 100000, JT_MITER, 2); push @good_perimeters, $loop if @$offsets; } Slic3r::debugf "removed %d unprintable perimeters\n", (@{$self->perimeters} - @good_perimeters) diff --git a/lib/Slic3r/Perimeter.pm b/lib/Slic3r/Perimeter.pm index 5a4183282..3d26588c0 100644 --- a/lib/Slic3r/Perimeter.pm +++ b/lib/Slic3r/Perimeter.pm @@ -75,7 +75,7 @@ sub make_perimeter { } # generate skirt on bottom layer - if ($layer->id == 0 && $Slic3r::skirts > 0) { + if ($layer->id == 0 && $Slic3r::skirts > 0 && @{ $layer->surfaces }) { # find out convex hull my $points = [ map { @{ $_->mgp_polygon->polygons->[0] } } @{ $layer->surfaces } ]; my $convex_hull = $self->_mgp_from_points_ref($points)->convexhull2; # maybe Math::ConvexHull is faster? @@ -84,7 +84,7 @@ sub make_perimeter { # draw outlines from outside to inside for (my $i = $Slic3r::skirts - 1; $i >= 0; $i--) { my $distance = ($Slic3r::skirt_distance + ($Slic3r::flow_width * $i)) / $Slic3r::resolution; - my $outline = offset([$convex_hull_points], $distance, 0.1, JT_ROUND); + my $outline = offset([$convex_hull_points], $distance, $Slic3r::resolution * 100000, JT_ROUND); push @{ $layer->skirts }, Slic3r::ExtrusionLoop->cast([ @{$outline->[0]} ]); } } @@ -101,7 +101,7 @@ sub offset_polygon { my ($contour_p, @holes_p) = ($polygon->{outer}, @{$polygon->{holes}}); # generate offsets - my $offsets = offset([ $contour_p, @holes_p ], -$distance, 100, JT_MITER, 2); + my $offsets = offset([ $contour_p, @holes_p ], -$distance, $Slic3r::resolution * 100000, JT_MITER, 2); # defensive programming my (@contour_offsets, @hole_offsets) = (); diff --git a/lib/Slic3r/STL.pm b/lib/Slic3r/STL.pm index a2991ef30..cf9ade6d4 100644 --- a/lib/Slic3r/STL.pm +++ b/lib/Slic3r/STL.pm @@ -30,7 +30,7 @@ sub parse_file { # we only want to work with positive coordinates, so let's # find our object extents to calculate coordinate displacements - my @extents = (map [99999999, -99999999], X,Y,Z); + my @extents = (map [99999999999, -99999999999], X,Y,Z); foreach my $facet ($stl->part->facets) { my ($normal, @vertices) = @$facet; foreach my $vertex (@vertices) { @@ -103,7 +103,7 @@ sub _facet { if $Slic3r::debug; # find the vertical extents of the facet - my ($min_z, $max_z) = (99999999, -99999999); + my ($min_z, $max_z) = (99999999999, -99999999999); foreach my $vertex (@vertices) { $min_z = $vertex->[Z] if $vertex->[Z] < $min_z; $max_z = $vertex->[Z] if $vertex->[Z] > $max_z; @@ -111,8 +111,10 @@ sub _facet { Slic3r::debugf "z: min = %.0f, max = %.0f\n", $min_z, $max_z; # calculate the layer extents - my ($min_layer, $max_layer) = map { sprintf '%.0f', $_ * $Slic3r::resolution / $Slic3r::layer_height } $min_z, $max_z; - Slic3r::debugf "layers: min = %.0f, max = %.0f\n", $min_layer, $max_layer; + my $min_layer = int($min_z * $Slic3r::resolution / $Slic3r::layer_height); + my $max_layer = int(0.99999 + ($max_z * $Slic3r::resolution / $Slic3r::layer_height)); + + Slic3r::debugf "layers: min = %s, max = %s\n", $min_layer, $max_layer; # is the facet horizontal? if ($min_layer == $max_layer) { @@ -149,48 +151,72 @@ sub _facet { return; } - # build the three segments of the triangle facet - my @edges = ( - [ $vertices[0], $vertices[1] ], - [ $vertices[1], $vertices[2] ], - [ $vertices[2], $vertices[0] ], - ); - for (my $layer_id = $min_layer; $layer_id <= $max_layer; $layer_id++) { my $layer = $print->layer($layer_id); - my $z = $layer->z; - - my @intersection_points = (); - - foreach my $edge (@edges) { - my ($a, $b) = @$edge; - if ($a->[Z] == $b->[Z] && $a->[Z] == $z) { - # edge is horizontal and belongs to the current layer - $layer->add_line([$a->[X], $a->[Y]], [$b->[X], $b->[Y]]); - - } elsif (($a->[Z] < $z && $b->[Z] > $z) || ($b->[Z] < $z && $a->[Z] > $z)) { - # edge intersects the current layer; calculate intersection - push @intersection_points, Slic3r::Point->cast([ - $b->[X] + ($a->[X] - $b->[X]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]), - $b->[Y] + ($a->[Y] - $b->[Y]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]), - ]); - } - } - - if (@intersection_points) { - # defensive programming: - die "Facets must intersect each plane 0 or 2 times" if @intersection_points != 2; - - # check whether the two points coincide due to resolution rounding - if ($intersection_points[0]->coincides_with($intersection_points[1])) { - Slic3r::debugf "Points coincide at layer %d; removing\n", $layer_id; - next; - } - - # connect points: - $layer->add_line(@intersection_points); - } + $layer->add_line($_) for $self->intersect_facet(\@vertices, $layer->z); } } +sub intersect_facet { + my $self = shift; + my ($vertices, $z) = @_; + + # build the three segments of the triangle facet + my @edges = ( + [ $vertices->[0], $vertices->[1] ], + [ $vertices->[1], $vertices->[2] ], + [ $vertices->[2], $vertices->[0] ], + ); + + my (@lines, @intersection_points) = (); + + foreach my $edge (@edges) { + my ($a, $b) = @$edge; + #printf "Az = %d, Bz = %d, z = %d\n", $a->[Z], $b->[Z], $z; + + if ($a->[Z] == $b->[Z] && $a->[Z] == $z) { + # edge is horizontal and belongs to the current layer + push @lines, [ [$a->[X], $a->[Y]], [$b->[X], $b->[Y]] ]; + #print "Horizontal!\n"; + + } elsif (($a->[Z] < $z && $b->[Z] > $z) || ($b->[Z] < $z && $a->[Z] > $z)) { + # edge intersects the current layer; calculate intersection + push @intersection_points, [ + $b->[X] + ($a->[X] - $b->[X]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]), + $b->[Y] + ($a->[Y] - $b->[Y]) * ($z - $b->[Z]) / ($a->[Z] - $b->[Z]), + ]; + #print "Intersects!\n"; + + } elsif ($a->[Z] == $z) { + #print "A point on plane!\n"; + push @intersection_points, [ $a->[X], $a->[Y] ]; + + } elsif ($b->[Z] == $z) { + #print "B point on plane!\n"; + push @intersection_points, [ $b->[X], $b->[Y] ]; + } + } + + Slic3r::Geometry::remove_coinciding_points(\@intersection_points); + + if (@intersection_points > 1 && !@lines) { + + # remove coinciding points + + # defensive programming: + die "Facets must intersect each plane 0 or 2 times" if @intersection_points != 2; + + # check whether the two points coincide due to resolution rounding + #if ($intersection_points[0]->coincides_with($intersection_points[1])) { + # Slic3r::debugf "Points coincide; removing\n"; + # return; + #} + + # connect points: + push @lines, [ @intersection_points ]; + } + + return @lines; +} + 1; diff --git a/lib/Slic3r/SVG.pm b/lib/Slic3r/SVG.pm index d66dca3ff..a8e91da93 100644 --- a/lib/Slic3r/SVG.pm +++ b/lib/Slic3r/SVG.pm @@ -8,7 +8,7 @@ use constant X => 0; use constant Y => 1; sub factor { - return $Slic3r::resolution * 100; + return $Slic3r::resolution * 10; } sub svg { diff --git a/t/stl.t b/t/stl.t new file mode 100644 index 000000000..901d8c33e --- /dev/null +++ b/t/stl.t @@ -0,0 +1,37 @@ +use Test::More; + +plan tests => 7; + +BEGIN { + use FindBin; + use lib "$FindBin::Bin/../lib"; +} + +use Slic3r; + +my $stl = Slic3r::STL->new; + +my @lines; +my $z = 20; +my @points = ([3, 4], [8, 5], [1, 9]); + +is_deeply lines(20, 20, 20), [ + [ $points[0], $points[1] ], + [ $points[1], $points[2] ], + [ $points[2], $points[0] ], +], 'horizontal'; + +is_deeply lines(22, 20, 20), [ [ $points[1], $points[2] ] ], 'lower edge on layer'; +is_deeply lines(20, 20, 10), [ [ $points[0], $points[1] ] ], 'upper edge on layer'; +is_deeply lines(20, 15, 10), [ ], 'upper vertex on layer'; +is_deeply lines(28, 20, 30), [ ], 'lower vertex on layer'; +is_deeply lines(24, 10, 16), [ [ [4, 4], [2, 6] ] ], 'two edges intersect'; +is_deeply lines(24, 10, 20), [ [ [4, 4], [1, 9] ] ], 'one vertex on plane and one edge intersects'; + +sub vertices { + [ map [ @{$points[$_]}, $_[$_] ], 0..2 ] +} + +sub lines { + [ map [ map ref $_ eq 'Slic3r::Point' ? $_->p : [ map sprintf('%.0f', $_), @$_ ], @$_ ], $stl->intersect_facet(vertices(@_), $z, $dz) ]; +}