From b5426ad297acd311912862b6ea2eca80580aaa95 Mon Sep 17 00:00:00 2001 From: Mark Hindess Date: Tue, 16 Jul 2013 10:44:52 +0100 Subject: [PATCH 01/52] Revert "Optimization: simplify fill_surfaces before the offset operation" To workaround issue #1325 and possibly #1320. This reverts commit 3a046e34111aeaf3701062f006e9f5957635e15b. --- lib/Slic3r/Layer/Region.pm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 6aa6340413..6d1d4745e2 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -209,11 +209,12 @@ sub make_perimeters { # and then we offset back and forth by the infill spacing to only consider the # non-collapsing regions push @{ $self->fill_surfaces }, - offset2_ex( - [ map $_->simplify(&Slic3r::SCALED_RESOLUTION), @last ], - -($perimeter_spacing/2 + $infill_spacing), - +$infill_spacing, - ); + map $_->simplify(&Slic3r::SCALED_RESOLUTION), + offset2_ex( + \@last, + -($perimeter_spacing/2 + $infill_spacing), + +$infill_spacing, + ); } $self->_fill_gaps(\@gaps); From 943304887303e64f4cde90c7c315dff322653c55 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 19 Jul 2013 22:49:39 +0200 Subject: [PATCH 02/52] One more test about polygon simplification --- t/clean_polylines.t | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/t/clean_polylines.t b/t/clean_polylines.t index 17c4ad6abb..84acab55e1 100644 --- a/t/clean_polylines.t +++ b/t/clean_polylines.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 7; +plan tests => 8; BEGIN { use FindBin; @@ -79,6 +79,9 @@ use Slic3r; ok @simplified == 1, 'gear simplified to a single polygon'; note sprintf "original points: %d\nnew points: %d", $num_points, scalar(@{$simplified[0]}); ok @{$simplified[0]} < $num_points, 'gear was further simplified using Douglas-Peucker'; + + my @simplified_ex = Slic3r::ExPolygon->new($polygon)->simplify(10); + is_deeply \@simplified_ex, [ \@simplified ], 'simplified polygon equals simplified expolygon'; } { From e29aca35539ee7a85eb02b95e22a07f0834ec5fc Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jul 2013 12:22:41 +0200 Subject: [PATCH 03/52] Reapply correct optimization for simplifiying fill_surfaces before performing the offset. #1325 --- lib/Slic3r/ExPolygon.pm | 12 +++++++++--- lib/Slic3r/Geometry/Clipper.pm | 6 ++++-- lib/Slic3r/Layer/Region.pm | 11 +++++------ lib/Slic3r/Polygon.pm | 2 ++ t/clean_polylines.t | 21 ++++++++++++++++++++- 5 files changed, 40 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index c58af979f9..610751a629 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -145,15 +145,21 @@ sub clip_line { return Boost::Geometry::Utils::polygon_multi_linestring_intersection($self, [$line]); } -sub simplify { +sub simplify_as_polygons { my $self = shift; my ($tolerance) = @_; # it would be nice to have a multilinestring_simplify method in B::G::U - my @simplified = Slic3r::Geometry::Clipper::simplify_polygons( + return Slic3r::Geometry::Clipper::simplify_polygons( [ map Boost::Geometry::Utils::linestring_simplify($_, $tolerance), @$self ], ); - return @{ Slic3r::Geometry::Clipper::union_ex([ @simplified ]) }; +} + +sub simplify { + my $self = shift; + my ($tolerance) = @_; + + return @{ Slic3r::Geometry::Clipper::union_ex([ $self->simplify_as_polygons($tolerance) ]) }; } sub scale { diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index aa124ba879..35d13c874b 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -150,12 +150,14 @@ sub collapse_ex { sub simplify_polygon { my ($polygon, $pft) = @_; - return @{ Math::Clipper::simplify_polygon($polygon, $pft // PFT_NONZERO) }; + return map Slic3r::Polygon->new(@$_), + @{ Math::Clipper::simplify_polygon($polygon, $pft // PFT_NONZERO) }; } sub simplify_polygons { my ($polygons, $pft) = @_; - return @{ Math::Clipper::simplify_polygons($polygons, $pft // PFT_NONZERO) }; + return map Slic3r::Polygon->new(@$_), + @{ Math::Clipper::simplify_polygons($polygons, $pft // PFT_NONZERO) }; } sub traverse_pt { diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 6d1d4745e2..4441215d7a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -209,12 +209,11 @@ sub make_perimeters { # and then we offset back and forth by the infill spacing to only consider the # non-collapsing regions push @{ $self->fill_surfaces }, - map $_->simplify(&Slic3r::SCALED_RESOLUTION), - offset2_ex( - \@last, - -($perimeter_spacing/2 + $infill_spacing), - +$infill_spacing, - ); + offset2_ex( + [ map $_->simplify_as_polygons(&Slic3r::SCALED_RESOLUTION), @{union_ex(\@last)} ], + -($perimeter_spacing/2 + $infill_spacing), + +$infill_spacing, + ); } $self->_fill_gaps(\@gaps); diff --git a/lib/Slic3r/Polygon.pm b/lib/Slic3r/Polygon.pm index f743ac5435..8b4fc745ad 100644 --- a/lib/Slic3r/Polygon.pm +++ b/lib/Slic3r/Polygon.pm @@ -72,6 +72,8 @@ sub grow { return $self->split_at_first_point->grow(@_); } +# NOTE that this will turn the polygon to ccw regardless of its +# original orientation sub simplify { my $self = shift; return Slic3r::Geometry::Clipper::simplify_polygon( $self->SUPER::simplify(@_) ); diff --git a/t/clean_polylines.t b/t/clean_polylines.t index 84acab55e1..6427021f4e 100644 --- a/t/clean_polylines.t +++ b/t/clean_polylines.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 8; +plan tests => 10; BEGIN { use FindBin; @@ -84,6 +84,25 @@ use Slic3r; is_deeply \@simplified_ex, [ \@simplified ], 'simplified polygon equals simplified expolygon'; } +{ + my $square = Slic3r::Polygon->new( # ccw + [100, 100], + [200, 100], + [200, 200], + [100, 200], + ); + my $hole_in_square = Slic3r::Polygon->new( # cw + [140, 140], + [140, 160], + [160, 160], + [160, 140], + ); + my $expolygon = Slic3r::ExPolygon->new($square, $hole_in_square); + my @simplified = $hole_in_square->simplify; + is scalar(@simplified), 1, 'hole simplification returns one polygon'; + ok $simplified[0]->is_counter_clockwise, 'hole simplification turns cw polygon into ccw polygon'; +} + { my $circle = [ [3744.8,8045.8],[3788.1,8061.4],[3940.6,8116.3],[3984.8,8129.2],[4140.6,8174.4],[4185.5,8184.4],[4343.8,8219.9], From b1147861ddcc2f3e06790a2e4d08b243f49f5777 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 20 Jul 2013 21:19:59 +0200 Subject: [PATCH 04/52] Implement lower_bound() and upper_bound() methods for ZTable --- lib/Slic3r/Print/Object.pm | 12 +++++++++--- xs/t/02_object.t | 15 ++++++++++++++- xs/xsp/Object.xsp | 20 ++++++++++++++++++++ 3 files changed, 43 insertions(+), 4 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index ae7228ff37..1c21206d0c 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -16,7 +16,7 @@ has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] has 'fill_maker' => (is => 'lazy'); -has '_z_table' => (is => 'lazy'); +has '_slice_z_table' => (is => 'lazy'); sub BUILD { my $self = shift; @@ -84,7 +84,7 @@ sub _build_fill_maker { return Slic3r::Fill->new(object => $self); } -sub _build__z_table { +sub _build__slice_z_table { my $self = shift; return Slic3r::Object::XS::ZTable->new([ map $_->slice_z, @{$self->layers} ]); } @@ -105,7 +105,13 @@ sub layer_count { sub get_layer_range { my $self = shift; - return @{ $self->_z_table->get_range(@_) }; + my ($min_z, $max_z) = @_; + + my $min_layer = $self->_slice_z_table->lower_bound($min_z); # first layer whose slice_z is >= $min_z + return ( + $min_layer, + $self->_slice_z_table->upper_bound($max_z, $min_layer)-1, # last layer whose slice_z is <= $max_z + ); } sub bounding_box { diff --git a/xs/t/02_object.t b/xs/t/02_object.t index 4ceddeda30..53a6f869b9 100644 --- a/xs/t/02_object.t +++ b/xs/t/02_object.t @@ -4,9 +4,22 @@ use strict; use warnings; use Slic3r::XS; -use Test::More tests => 1; +use Test::More tests => 10; my $table = Slic3r::Object::XS::ZTable->new([ 10, 20, 30, 40, 50, 60, 70, 80, 90, 100 ]); +is_deeply $table->get_range(31, 61), [2, 6], 'get_layer_range'; is_deeply $table->get_range(39, 69), [2, 6], 'get_layer_range'; +is_deeply $table->get_range(30, 60), [2, 5], 'get_layer_range'; + +# upper_bound points to the first element that is greater than argument +is $table->upper_bound(30), 3, 'upper_bound'; +is $table->upper_bound(31), 3, 'upper_bound'; +is $table->upper_bound(39), 3, 'upper_bound'; +is $table->upper_bound(39, 4), 4, 'upper_bound with offset'; + +# lower_bound points to the first element that is not less than argument +is $table->lower_bound(31), 3, 'lower_bound'; +is $table->lower_bound(39), 3, 'lower_bound'; +is $table->lower_bound(40), 3, 'lower_bound'; __END__ diff --git a/xs/xsp/Object.xsp b/xs/xsp/Object.xsp index 2f8a0601ea..9e800cdf8f 100644 --- a/xs/xsp/Object.xsp +++ b/xs/xsp/Object.xsp @@ -8,6 +8,7 @@ %name{Slic3r::Object::XS::ZTable} class ZTable { ZTable(std::vector* z_array); + ~ZTable(); %{ std::vector @@ -47,6 +48,25 @@ get_range(THIS, min_z, max_z) } OUTPUT: RETVAL + +unsigned int +ZTable::lower_bound(z, offset = 0) + unsigned int z + unsigned int offset + CODE: + RETVAL = std::lower_bound(THIS->z.begin() + offset, THIS->z.end(), z) - THIS->z.begin(); + OUTPUT: + RETVAL + +unsigned int +ZTable::upper_bound(z, offset = 0) + unsigned int z + unsigned int offset + CODE: + RETVAL = std::upper_bound(THIS->z.begin() + offset, THIS->z.end(), z) - THIS->z.begin(); + OUTPUT: + RETVAL + %} }; From 9d13a90837a7b019ccda6847205a285a7cf73147 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Jul 2013 11:36:18 +0200 Subject: [PATCH 05/52] Missing #include causing XS build to fail. #1349 --- xs/xsp/Object.xsp | 1 + 1 file changed, 1 insertion(+) diff --git a/xs/xsp/Object.xsp b/xs/xsp/Object.xsp index 9e800cdf8f..bdfef05390 100644 --- a/xs/xsp/Object.xsp +++ b/xs/xsp/Object.xsp @@ -4,6 +4,7 @@ #include #include "ZTable.hpp" #include +#include %} %name{Slic3r::Object::XS::ZTable} class ZTable { From aa2ad3bbd2f4533e869a5a756d06cd017d2b0ac7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Jul 2013 23:18:22 +0200 Subject: [PATCH 06/52] Fix garbage collection of shared data. #1348 --- lib/Slic3r.pm | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index a3761ac6b9..aac355b363 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -86,7 +86,12 @@ sub parallelize { my $q = Thread::Queue->new; $q->enqueue(@items, (map undef, 1..$Config->threads)); - my $thread_cb = sub { $params{thread_cb}->($q) }; + my $thread_cb = sub { + my $result = $params{thread_cb}->($q); + Slic3r::thread_cleanup(); + return $result; + }; + @_ = (); foreach my $th (map threads->create($thread_cb), 1..$Config->threads) { $params{collect_cb}->($th->join); @@ -96,6 +101,18 @@ sub parallelize { } } +# call this at the very end of each thread (except the main one) +# so that it does not try to free existing objects. +# at that stage, existing objects are only those that we +# inherited at the thread creation (thus shared) and those +# that we are returning: destruction will be handled by the +# main thread in both cases. +sub thread_cleanup { + # prevent destruction of shared objects + no warnings 'redefine'; + *Slic3r::Object::XS::ZTable::DESTROY = sub {}; +} + sub encode_path { my ($filename) = @_; return encode('locale_fs', $filename); From 4bc1c6e3d89b4ea29ea9f301c44a3db1ed3f52e0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 23 Jul 2013 23:27:06 +0200 Subject: [PATCH 07/52] Update MANIFEST* --- xs/MANIFEST | 11 ------- xs/MANIFEST.SKIP | 73 ++++++++++++++++++++++++++++++++++++++++++++ xs/MANIFEST.SKIP.bak | 15 --------- 3 files changed, 73 insertions(+), 26 deletions(-) create mode 100644 xs/MANIFEST.SKIP delete mode 100644 xs/MANIFEST.SKIP.bak diff --git a/xs/MANIFEST b/xs/MANIFEST index a803710ffc..046103dc90 100644 --- a/xs/MANIFEST +++ b/xs/MANIFEST @@ -1,30 +1,19 @@ Build.PL -buildtmp/main.xs -buildtmp/typemap -buildtmp/XS.c -buildtmp/XS.o lib/Slic3r/XS.pm MANIFEST This list of files src/admesh/connect.c -src/admesh/connect.o src/admesh/normals.c -src/admesh/normals.o src/admesh/shared.c -src/admesh/shared.o src/admesh/stl.h src/admesh/stl_io.c -src/admesh/stl_io.o src/admesh/stlinit.c -src/admesh/stlinit.o src/admesh/util.c -src/admesh/util.o src/ExPolygon.hpp src/myinit.h src/Point.hpp src/ppport.h src/TriangleMesh.cpp src/TriangleMesh.hpp -src/TriangleMesh.o src/ZTable.hpp t/01_trianglemesh.t t/02_object.t diff --git a/xs/MANIFEST.SKIP b/xs/MANIFEST.SKIP new file mode 100644 index 0000000000..0123bf375f --- /dev/null +++ b/xs/MANIFEST.SKIP @@ -0,0 +1,73 @@ + +#!start included /Library/Perl/Updates/5.12.4/ExtUtils/MANIFEST.SKIP +# Avoid version control files. +\bRCS\b +\bCVS\b +\bSCCS\b +,v$ +\B\.svn\b +\B\.git\b +\B\.gitignore\b +\b_darcs\b +\B\.cvsignore$ + +# Avoid VMS specific MakeMaker generated files +\bDescrip.MMS$ +\bDESCRIP.MMS$ +\bdescrip.mms$ + +# Avoid Makemaker generated and utility files. +\bMANIFEST\.bak +\bMakefile$ +\bblib/ +\bMakeMaker-\d +\bpm_to_blib\.ts$ +\bpm_to_blib$ +\bblibdirs\.ts$ # 6.18 through 6.25 generated this + +# Avoid Module::Build generated and utility files. +\bBuild$ +\b_build/ +\bBuild.bat$ +\bBuild.COM$ +\bBUILD.COM$ +\bbuild.com$ + +# Avoid temp and backup files. +~$ +\.old$ +\#$ +\b\.# +\.bak$ +\.tmp$ +\.# +\.rej$ + +# Avoid OS-specific files/dirs +# Mac OSX metadata +\B\.DS_Store +# Mac OSX SMB mount metadata files +\B\._ + +# Avoid Devel::Cover and Devel::CoverX::Covered files. +\bcover_db\b +\bcovered\b + +# Avoid MYMETA files +^MYMETA\. +#!end included /Library/Perl/Updates/5.12.4/ExtUtils/MANIFEST.SKIP + +# Avoid configuration metadata file +^MYMETA\. + +# Avoid Module::Build generated and utility files. +\bBuild$ +\bBuild.bat$ +\b_build +\bBuild.COM$ +\bBUILD.COM$ +\bbuild.com$ +^MANIFEST\.SKIP + +# Avoid archives of this distribution +\bSlic3r-XS-[\d\.\_]+ diff --git a/xs/MANIFEST.SKIP.bak b/xs/MANIFEST.SKIP.bak deleted file mode 100644 index 87d34ec10a..0000000000 --- a/xs/MANIFEST.SKIP.bak +++ /dev/null @@ -1,15 +0,0 @@ -#!include_default -# Avoid configuration metadata file -^MYMETA\. - -# Avoid Module::Build generated and utility files. -\bBuild$ -\bBuild.bat$ -\b_build -\bBuild.COM$ -\bBUILD.COM$ -\bbuild.com$ -^MANIFEST\.SKIP - -# Avoid archives of this distribution -\bSlic3r-XS-[\d\.\_]+ From 2b8662cf0ce55bddd5fc70eeef0f76a6e05844c0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 24 Jul 2013 10:06:02 +0200 Subject: [PATCH 08/52] Temporary workarond for an upstream bug in Moo which causes failure when running with threads. #1330 --- lib/Slic3r.pm | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/Slic3r.pm b/lib/Slic3r.pm index aac355b363..ad6157ec11 100644 --- a/lib/Slic3r.pm +++ b/lib/Slic3r.pm @@ -19,6 +19,10 @@ our $have_threads; BEGIN { use Config; $have_threads = $Config{useithreads} && eval "use threads; use threads::shared; use Thread::Queue; 1"; + + ### temporarily disable threads if using the broken Moo version + use Moo; + $have_threads = 0 if $Moo::VERSION == 1.003000; } warn "Running Slic3r under Perl >= 5.16 is not supported nor recommended\n" From 8fe228fceeca212242a8b572260e8a50efdb0842 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 00:03:28 +0200 Subject: [PATCH 09/52] Smarter ordering of gap fill --- lib/Slic3r/Fill.pm | 6 ++++-- lib/Slic3r/Layer/Region.pm | 17 ++++++++++++++--- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index d88def8356..5270c7250d 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -196,8 +196,10 @@ sub make_fill { } # add thin fill regions - push @fills, @{$layerm->thin_fills}; - push @fills_ordering_points, map $_->unpack->points->[0], @{$layerm->thin_fills}; + if (@{ $layerm->thin_fills }) { + push @fills, Slic3r::ExtrusionPath::Collection->new(paths => $layerm->thin_fills); + push @fills_ordering_points, $fills[-1]->first_point; + } # organize infill paths using a nearest-neighbor search @fills = @fills[ chained_path(\@fills_ordering_points) ]; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 4441215d7a..9e76f09f12 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -348,12 +348,11 @@ sub _fill_gaps { my @infill = map $_->offset_ex(-0.5*$flow->scaled_width), @this_width; foreach my $expolygon (@infill) { - my @paths = $filler->fill_surface( + my ($params, @paths) = $filler->fill_surface( Slic3r::Surface->new(expolygon => $expolygon), density => 1, flow_spacing => $flow->spacing, ); - my $params = shift @paths; push @{ $self->thin_fills }, map { @@ -365,7 +364,19 @@ sub _fill_gaps { role => EXTR_ROLE_GAPFILL, height => $self->height, flow_spacing => $params->{flow_spacing}, - ), @paths; + ), + # Split polylines into lines so that the chained_path() search + # at the final stage has more freedom and will choose starting + # points closer than last positions. OTOH, this will make such + # search slower. Probably, ExtrusionPath objects should support + # splitting nearby a given position so that we can choose the right + # entry point even in the middle of the path without needing a + # complex, slow, chained_path() search on all segments. TODO. + # Such logic will also avoid all the small travel moves that this + # line-splitting causes, and it will be applicable to other things + # too. + map Slic3r::Polyline->new(@$_)->lines, + @paths; } } From 37bf0fa53ba19f36db3208881aabb16faf62ce36 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 00:13:24 +0200 Subject: [PATCH 10/52] Bugfix: medial axis thin wall detection was triggered when not needed --- lib/Slic3r/Layer/Region.pm | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 9e76f09f12..16baf95d9a 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -93,21 +93,21 @@ sub make_surfaces { # detect thin walls by offsetting slices by half extrusion inwards if ($Slic3r::Config->thin_walls) { - my $width = $self->perimeter_flow->scaled_width; + $self->thin_walls([]); + # we use spacing here because there could be a case where + # the slice collapses with width but doesn't collapse with spacing, + # thus causing both perimeters and medial axis to be generated + my $width = $self->perimeter_flow->scaled_spacing; my $diff = diff_ex( [ map $_->p, @{$self->slices} ], - [ offset2([ map @$_, map $_->expolygon, @{$self->slices} ], -$width, +$width) ], + [ offset2([ map $_->p, @{$self->slices} ], -$width*0.5, +$width*0.5) ], 1, ); - $self->thin_walls([]); - if (@$diff) { - my $area_threshold = $self->perimeter_flow->scaled_spacing ** 2; - @$diff = grep $_->area > ($area_threshold), @$diff; - - @{$self->thin_walls} = map $_->medial_axis($self->perimeter_flow->scaled_width), @$diff; - - Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}) if @{$self->thin_walls}; + my $area_threshold = $width ** 2; + if (@$diff = grep { $_->area > $area_threshold } @$diff) { + @{$self->thin_walls} = map $_->medial_axis($width), @$diff; + Slic3r::debugf " %d thin walls detected\n", scalar(@{$self->thin_walls}); } } From fe94e31bda044cd097bf8ff43773fdbf37411a74 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 10:17:21 +0200 Subject: [PATCH 11/52] Remove t/freeze.t. #1352 --- t/freeze.t | 30 ------------------------------ 1 file changed, 30 deletions(-) delete mode 100644 t/freeze.t diff --git a/t/freeze.t b/t/freeze.t deleted file mode 100644 index 07fd020d29..0000000000 --- a/t/freeze.t +++ /dev/null @@ -1,30 +0,0 @@ -use Test::More tests => 1; -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; -} - -use Slic3r; -use Slic3r::Test; -use Storable qw(nstore retrieve); -use Time::HiRes qw(gettimeofday tv_interval); - -{ - my $t0 = [gettimeofday]; - my $print = Slic3r::Test::init_print('20mm_cube', scale => 2); - my $gcode = Slic3r::Test::gcode($print); - ###diag sprintf 'Slicing took %s seconds', tv_interval($t0); - - my $t1 = [gettimeofday]; - nstore $print, 'print.dat'; - $print = retrieve 'print.dat'; - unlink 'print.dat'; - ###diag sprintf 'Freezing and retrieving took %s seconds', tv_interval($t1); - - isa_ok $print, 'Slic3r::Print', 'restored Print object'; -} - -__END__ From c69edf27e940ed2765d65fc584854b973d9602e7 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 10:52:22 +0200 Subject: [PATCH 12/52] Bugfix: only_retract_when_crossing_perimeters was not triggering retraction when moving between islands that are covered/bridged on the above layer. #1308 --- lib/Slic3r/GCode.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index ccfadc57cc..71a9afbe86 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -350,8 +350,12 @@ sub travel_to { # build a more complete configuration space $travel->translate(-$self->shift_x, -$self->shift_y); + # skip retraction if the travel move is contained in an island in the current layer + # *and* in an island in the upper layer (so that the ooze will not be visible) if ($travel->length < scale $self->extruder->retract_before_travel - || ($self->config->only_retract_when_crossing_perimeters && first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) + || ($self->config->only_retract_when_crossing_perimeters + && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->upper_layer_slices}) + && (first { $_->encloses_line($travel, scaled_epsilon) } @{$self->layer->slices})) || ($role == EXTR_ROLE_SUPPORTMATERIAL && $self->layer->support_islands_enclose_line($travel)) ) { $self->straight_once(0); From a145f1b6aaf9fa0b4f4e97e59c3c71cb918dddbe Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 12:31:25 +0200 Subject: [PATCH 13/52] Don't merge adjacent bridges so that more correct angles can be detected for each one --- lib/Slic3r/Layer/Region.pm | 310 ++++++++++++++++++------------------- 1 file changed, 152 insertions(+), 158 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 16baf95d9a..653931d8d4 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -411,177 +411,171 @@ sub prepare_fill_surfaces { sub process_external_surfaces { my $self = shift; - # enlarge top and bottom surfaces - { - # get all external surfaces - my @top = grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}; - my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; + my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters + + my @bottom = (); + foreach my $surface (grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}) { + my ($grown) = $surface->expolygon->offset_ex(+$margin); - # if we're slicing with no infill, we can't extend external surfaces - # over non-existent infill - my @fill_boundaries = $Slic3r::Config->fill_density > 0 - ? @{$self->fill_surfaces} - : grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces}; + # detect bridge direction before merging grown surfaces otherwise adjacent bridges + # would get merged into a single one while they need different directions + my $angle = $self->id > 0 + ? $self->_detect_bridge_direction($grown) + : undef; - # offset them and intersect the results with the actual fill boundaries - my $margin = scale 3; # TODO: ensure this is greater than the total thickness of the perimeters - @top = @{intersection_ex( - [ Slic3r::Geometry::Clipper::offset([ map $_->p, @top ], +$margin) ], - [ map $_->p, @fill_boundaries ], - undef, - 1, # to ensure adjacent expolygons are unified - )}; - @bottom = @{intersection_ex( - [ Slic3r::Geometry::Clipper::offset([ map $_->p, @bottom ], +$margin) ], - [ map $_->p, @fill_boundaries ], - undef, - 1, # to ensure adjacent expolygons are unified - )}; - - # give priority to bottom surfaces - @top = @{diff_ex( - [ map @$_, @top ], - [ map @$_, @bottom ], - )}; - - # generate new surfaces - my @new_surfaces = (); - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_TOP, - ), @top; - push @new_surfaces, map Slic3r::Surface->new( - expolygon => $_, - surface_type => S_TYPE_BOTTOM, - ), @bottom; - - # subtract the new top surfaces from the other non-top surfaces and re-add them - my @other = grep $_->surface_type != S_TYPE_TOP && $_->surface_type != S_TYPE_BOTTOM, @{$self->fill_surfaces}; - foreach my $group (Slic3r::Surface->group(@other)) { - push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( - [ map $_->p, @$group ], - [ map $_->p, @new_surfaces ], - )}; - } - @{$self->fill_surfaces} = @new_surfaces; + push @bottom, $surface->clone(expolygon => $grown, bridge_angle => $angle); } - # detect bridge direction (skip bottom layer) - $self->_detect_bridges if $self->id > 0; + my @top = (); + foreach my $surface (grep $_->surface_type == S_TYPE_TOP, @{$self->fill_surfaces}) { + # give priority to bottom surfaces + my $grown = diff_ex( + [ $surface->expolygon->offset(+$margin) ], + [ map $_->p, @bottom ], + ); + push @top, map $surface->clone(expolygon => $_), @$grown; + } + + # if we're slicing with no infill, we can't extend external surfaces + # over non-existent infill + my @fill_boundaries = $Slic3r::Config->fill_density > 0 + ? @{$self->fill_surfaces} + : grep $_->surface_type != S_TYPE_INTERNAL, @{$self->fill_surfaces}; + + # intersect the grown surfaces with the actual fill boundaries + my @new_surfaces = (); + foreach my $group (Slic3r::Surface->group(@top, @bottom)) { + push @new_surfaces, + map $group->[0]->clone(expolygon => $_), + @{intersection_ex( + [ map $_->p, @$group ], + [ map $_->p, @fill_boundaries ], + undef, + 1, # to ensure adjacent expolygons are unified + )}; + } + + # subtract the new top surfaces from the other non-top surfaces and re-add them + my @other = grep $_->surface_type != S_TYPE_TOP && $_->surface_type != S_TYPE_BOTTOM, @{$self->fill_surfaces}; + foreach my $group (Slic3r::Surface->group(@other)) { + push @new_surfaces, map $group->[0]->clone(expolygon => $_), @{diff_ex( + [ map $_->p, @$group ], + [ map $_->p, @new_surfaces ], + )}; + } + @{$self->fill_surfaces} = @new_surfaces; } -sub _detect_bridges { +sub _detect_bridge_direction { my $self = shift; + my ($expolygon) = @_; - my @bottom = grep $_->surface_type == S_TYPE_BOTTOM, @{$self->fill_surfaces}; # surfaces - my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons + my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons - foreach my $surface (@bottom) { - # detect what edges lie on lower slices - my @edges = (); # polylines - foreach my $lower (@lower) { - # turn bridge contour and holes into polylines and then clip them - # with each lower slice's contour - my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @{$surface->expolygon}; - if (@clipped == 2) { - # If the split_at_first_point() call above happens to split the polygon inside the clipping area - # we would get two consecutive polylines instead of a single one, so we use this ugly hack to - # recombine them back into a single one in order to trigger the @edges == 2 logic below. - # This needs to be replaced with something way better. - if (points_coincide($clipped[0][0], $clipped[-1][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]})); - } - if (points_coincide($clipped[-1][0], $clipped[0][-1])) { - @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); - } + # detect what edges lie on lower slices + my @edges = (); # polylines + foreach my $lower (@lower) { + # turn bridge contour and holes into polylines and then clip them + # with each lower slice's contour + my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @$expolygon; + if (@clipped == 2) { + # If the split_at_first_point() call above happens to split the polygon inside the clipping area + # we would get two consecutive polylines instead of a single one, so we use this ugly hack to + # recombine them back into a single one in order to trigger the @edges == 2 logic below. + # This needs to be replaced with something way better. + if (points_coincide($clipped[0][0], $clipped[-1][-1])) { + @clipped = (Slic3r::Polyline->new(@{$clipped[-1]}, @{$clipped[0]})); } - push @edges, @clipped; - } - - Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); - next if !@edges; - - my $bridge_angle = undef; - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("bridge_$surface.svg", - expolygons => [ $surface->expolygon ], - red_expolygons => [ @lower ], - polylines => [ @edges ], - ); - } - - if (@edges == 2) { - my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; - my @midpoints = map $_->midpoint, @chords; - my $line_between_midpoints = Slic3r::Line->new(@midpoints); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); - } elsif (@edges == 1) { - # TODO: this case includes both U-shaped bridges and plain overhangs; - # we need a trapezoidation algorithm to detect the actual bridged area - # and separate it from the overhang area. - # in the mean time, we're treating as overhangs all cases where - # our supporting edge is a straight line - if (@{$edges[0]} > 2) { - my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); - $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + if (points_coincide($clipped[-1][0], $clipped[0][-1])) { + @clipped = (Slic3r::Polyline->new(@{$clipped[0]}, @{$clipped[1]})); } - } elsif (@edges) { - # inset the bridge expolygon; we'll use this one to clip our test lines - my $inset = [ $surface->expolygon->offset_ex($self->infill_flow->scaled_width) ]; - - # detect anchors as intersection between our bridge expolygon and the lower slices - my $anchors = intersection_ex( - [ $surface->p ], - [ map @$_, @lower ], - undef, - 1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges - ); - - # we'll now try several directions using a rudimentary visibility check: - # bridge in several directions and then sum the length of lines having both - # endpoints within anchors - my %directions = (); # angle => score - my $angle_increment = PI/36; # 5° - my $line_increment = $self->infill_flow->scaled_width; - for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { - # rotate everything - the center point doesn't matter - $_->rotate($angle, [0,0]) for @$inset, @$anchors; - - # generate lines in this direction - my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]); - my @lines = (); - for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) { - push @lines, [ [$x, $bounding_box->y_min], [$x, $bounding_box->y_max] ]; - } - - # TODO: use a multi_polygon_multi_linestring_intersection() call - my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; - - # remove any line not having both endpoints within anchors - @clipped_lines = grep { - my $line = $_; - !(first { $_->encloses_point_quick($line->[A]) } @$anchors) - && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); - } @clipped_lines; - - # sum length of bridged lines - $directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0; - } - - # this could be slightly optimized with a max search instead of the sort - my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; - - # the best direction is the one causing most lines to be bridged - $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); } - - Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", - $self->id, $bridge_angle if defined $bridge_angle; - - $surface->bridge_angle($bridge_angle); + push @edges, @clipped; } + + Slic3r::debugf "Found bridge on layer %d with %d support(s)\n", $self->id, scalar(@edges); + return undef if !@edges; + + my $bridge_angle = undef; + + if (0) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output("bridge_$expolygon.svg", + expolygons => [ $expolygon ], + red_expolygons => [ @lower ], + polylines => [ @edges ], + ); + } + + if (@edges == 2) { + my @chords = map Slic3r::Line->new($_->[0], $_->[-1]), @edges; + my @midpoints = map $_->midpoint, @chords; + my $line_between_midpoints = Slic3r::Line->new(@midpoints); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line_between_midpoints->direction); + } elsif (@edges == 1) { + # TODO: this case includes both U-shaped bridges and plain overhangs; + # we need a trapezoidation algorithm to detect the actual bridged area + # and separate it from the overhang area. + # in the mean time, we're treating as overhangs all cases where + # our supporting edge is a straight line + if (@{$edges[0]} > 2) { + my $line = Slic3r::Line->new($edges[0]->[0], $edges[0]->[-1]); + $bridge_angle = Slic3r::Geometry::rad2deg_dir($line->direction); + } + } elsif (@edges) { + # inset the bridge expolygon; we'll use this one to clip our test lines + my $inset = [ $expolygon->offset_ex($self->infill_flow->scaled_width) ]; + + # detect anchors as intersection between our bridge expolygon and the lower slices + my $anchors = intersection_ex( + [ @$expolygon ], + [ map @$_, @lower ], + undef, + 1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges + ); + + # we'll now try several directions using a rudimentary visibility check: + # bridge in several directions and then sum the length of lines having both + # endpoints within anchors + my %directions = (); # angle => score + my $angle_increment = PI/36; # 5° + my $line_increment = $self->infill_flow->scaled_width; + for (my $angle = 0; $angle <= PI; $angle += $angle_increment) { + # rotate everything - the center point doesn't matter + $_->rotate($angle, [0,0]) for @$inset, @$anchors; + + # generate lines in this direction + my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]); + my @lines = (); + for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) { + push @lines, [ [$x, $bounding_box->y_min], [$x, $bounding_box->y_max] ]; + } + + # TODO: use a multi_polygon_multi_linestring_intersection() call + my @clipped_lines = map @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection($_, \@lines) }, @$inset; + + # remove any line not having both endpoints within anchors + @clipped_lines = grep { + my $line = $_; + !(first { $_->encloses_point_quick($line->[A]) } @$anchors) + && !(first { $_->encloses_point_quick($line->[B]) } @$anchors); + } @clipped_lines; + + # sum length of bridged lines + $directions{-$angle} = sum(map Slic3r::Geometry::line_length($_), @clipped_lines) // 0; + } + + # this could be slightly optimized with a max search instead of the sort + my @sorted_directions = sort { $directions{$a} <=> $directions{$b} } keys %directions; + + # the best direction is the one causing most lines to be bridged + $bridge_angle = Slic3r::Geometry::rad2deg_dir($sorted_directions[-1]); + } + + Slic3r::debugf " Optimal infill angle of bridge on layer %d is %d degrees\n", + $self->id, $bridge_angle if defined $bridge_angle; + + return $bridge_angle; } 1; From c1c141683821284342285b8c80709fa9bacd6858 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 14:26:45 +0200 Subject: [PATCH 14/52] Scale layer height ranges too when scaling object in plater. #1284 --- lib/Slic3r/GUI/Plater.pm | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 49dcd1aea1..4c0943d42e 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -447,7 +447,7 @@ sub changescale { return if !$scale || $scale == -1; $self->{list}->SetItem($obj_idx, 2, "$scale%"); - $object->scale($scale / 100); + $object->changescale($scale / 100); $self->arrange; } @@ -1109,6 +1109,18 @@ sub _trigger_model_object { } } +sub changescale { + my $self = shift; + my ($scale) = @_; + + my $variation = $scale / $self->scale; + foreach my $range (@{ $self->layer_height_ranges }) { + $range->[0] *= $variation; + $range->[1] *= $variation; + } + $self->scale($scale); +} + sub check_manifoldness { my $self = shift; From aa194c81259f7495001fa4210934b7faca288922 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 14:30:00 +0200 Subject: [PATCH 15/52] Fix little regression causing object height not to be updated when object in plater was scaled --- lib/Slic3r/GUI/Plater.pm | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 4c0943d42e..9f3f956489 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1200,6 +1200,7 @@ sub transformed_bounding_box { my $bb = Slic3r::Geometry::BoundingBox->new_from_points($self->_apply_transform($self->convex_hull)); $bb->extents->[Z] = $self->bounding_box->clone->extents->[Z]; + $bb->extents->[Z][MAX] *= $self->scale; return $bb; } From debe540018c8137bb6407436ce2954e81194fd13 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 16:23:43 +0200 Subject: [PATCH 16/52] Better name for wipe --- lib/Slic3r/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index faa6fc06e5..a6c160acb4 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -832,7 +832,7 @@ END default => [1], }, 'wipe' => { - label => 'Wipe before retract', + label => 'Wipe while retracting', tooltip => 'This flag will move the nozzle while retracting to minimize the possible blob on leaky extruders.', cli => 'wipe!', type => 'bool', From 995c68b57f47af82b8f425e78383af160947c083 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 17:08:08 +0200 Subject: [PATCH 17/52] Can't NULL floats... --- xs/src/TriangleMesh.cpp | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/xs/src/TriangleMesh.cpp b/xs/src/TriangleMesh.cpp index 901bdfa47b..269db461a7 100644 --- a/xs/src/TriangleMesh.cpp +++ b/xs/src/TriangleMesh.cpp @@ -28,17 +28,17 @@ void TriangleMesh::ReadFromPerl(SV* vertices, SV* facets) for (unsigned int i = 0; i < stl.stats.number_of_facets; i++) { AV* facet_av = (AV*)SvRV(*av_fetch(facets_av, i, 0)); stl_facet facet; - facet.normal.x = NULL; - facet.normal.y = NULL; - facet.normal.z = NULL; + facet.normal.x = 0; + facet.normal.y = 0; + facet.normal.z = 0; for (unsigned int v = 0; v <= 2; v++) { AV* vertex_av = (AV*)SvRV(*av_fetch(vertices_av, SvIV(*av_fetch(facet_av, v, 0)), 0)); facet.vertex[v].x = SvNV(*av_fetch(vertex_av, 0, 0)); facet.vertex[v].y = SvNV(*av_fetch(vertex_av, 1, 0)); facet.vertex[v].z = SvNV(*av_fetch(vertex_av, 2, 0)); } - facet.extra[0] = NULL; - facet.extra[1] = NULL; + facet.extra[0] = 0; + facet.extra[1] = 0; stl.facet_start[i] = facet; } From f69dc7201dd4eea85966c02cfcd7989bf6a351b3 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 17:16:26 +0200 Subject: [PATCH 18/52] Perform a ./Build distclean in ./xs automatically --- Build.PL | 1 + 1 file changed, 1 insertion(+) diff --git a/Build.PL b/Build.PL index 063df21ede..07a7f0b3af 100644 --- a/Build.PL +++ b/Build.PL @@ -99,6 +99,7 @@ EOF # temporarily require this dev version until this upstream bug # is resolved: https://rt.cpan.org/Ticket/Display.html?id=86367 system $cpanm, 'SMUELLER/ExtUtils-ParseXS-3.18_04.tar.gz'; + system './xs/Build', 'distclean' if -e './xs/Build'; system $cpanm, '--reinstall', './xs'; } From 0a8872ca6ccac7ce61be31b03b723a4dab8fa65d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 19:25:15 +0200 Subject: [PATCH 19/52] Extend utils/dump-stl.pl to also write STL files --- lib/Slic3r/Test.pm | 2 ++ utils/dump-stl.pl | 13 ++++++++++++- 2 files changed, 14 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index dcb95d1302..b7a1972fd6 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -55,6 +55,8 @@ sub model { $facets = [ [0,1,2],[2,3,4],[2,5,0],[4,6,2],[2,6,5],[2,1,3],[7,8,9],[10,9,8],[11,9,10],[12,9,11],[9,13,14],[7,15,16],[10,17,0],[10,0,5],[12,11,6],[18,16,0],[6,19,13],[6,13,9],[9,12,6],[17,18,0],[11,10,5],[11,5,6],[14,16,15],[17,7,18],[16,18,7],[14,15,9],[7,9,15],[7,17,8],[10,8,17],[20,21,22],[23,24,25],[26,23,27],[28,27,23],[29,28,23],[30,29,23],[25,31,32],[22,33,34],[35,36,37],[24,38,39],[21,40,41],[38,42,20],[33,43,44],[6,4,23],[6,23,25],[36,35,1],[1,0,38],[1,38,36],[29,30,4],[25,32,6],[40,42,0],[35,45,1],[4,3,28],[4,28,29],[3,1,45],[3,45,28],[22,34,19],[19,6,32],[19,32,22],[42,38,0],[30,23,4],[0,16,43],[0,43,40],[24,37,36],[38,24,36],[24,23,37],[37,23,26],[22,32,20],[20,32,31],[33,41,40],[43,33,40],[45,35,26],[37,26,35],[33,44,34],[44,43,46],[20,42,21],[40,21,42],[31,39,38],[20,31,38],[33,22,41],[21,41,22],[31,25,39],[24,39,25],[26,27,45],[28,45,27],[47,48,49],[47,50,48],[51,48,50],[52,48,51],[53,48,52],[54,55,56],[57,55,54],[58,55,57],[49,59,47],[60,56,55],[59,56,60],[60,47,59],[48,53,16],[56,13,19],[54,56,19],[56,59,13],[59,49,14],[59,14,13],[49,48,16],[49,16,14],[44,46,60],[44,60,55],[51,50,43],[19,34,58],[19,58,57],[53,52,16],[43,16,52],[43,52,51],[57,54,19],[47,60,46],[55,58,34],[55,34,44],[50,47,46],[50,46,43] ], + } else { + return undef; } my $mesh = Slic3r::TriangleMesh->new( diff --git a/utils/dump-stl.pl b/utils/dump-stl.pl index a5c716b839..86543832b7 100644 --- a/utils/dump-stl.pl +++ b/utils/dump-stl.pl @@ -1,5 +1,6 @@ #!/usr/bin/perl # This script dumps a STL file into Perl syntax for writing tests +# or dumps a test model into a STL file use strict; use warnings; @@ -10,15 +11,24 @@ BEGIN { } use Slic3r; +use Slic3r::Test; $|++; $ARGV[0] or usage(1); -{ +if (-e $ARGV[0]) { my $model = Slic3r::Format::STL->read_file($ARGV[0]); my $mesh = $model->mesh; printf "VERTICES = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->vertices}; printf "FACETS = %s\n", join ',', map "[$_->[0],$_->[1],$_->[2]]", @{$mesh->facets}; + exit 0; +} elsif ((my $model = Slic3r::Test::model($ARGV[0]))) { + $ARGV[1] or die "Missing writeable destination as second argument\n"; + Slic3r::Format::STL->write_file($ARGV[1], $model); + printf "Model $ARGV[0] written to $ARGV[1]\n"; + exit 0; +} else { + die "No such model exists\n"; } @@ -27,6 +37,7 @@ sub usage { print <<"EOF"; Usage: dump-stl.pl file.stl + dump-stl.pl modelname file.stl EOF exit ($exit_code || 0); } From 6bd480361278144c7aff28baa8ddf46721405932 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Fri, 26 Jul 2013 20:17:33 +0200 Subject: [PATCH 20/52] Failing test case about spurious infill in hollow objects, caused by shells being correctly generated even for hollow objects - however sometimes we don't want that --- lib/Slic3r/Print/Object.pm | 2 +- lib/Slic3r/Test.pm | 1 + lib/Slic3r/TriangleMesh.pm | 10 ++++++++++ t/shells.t | 22 +++++++++++++++++++++- 4 files changed, 33 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 1c21206d0c..6ab3b40de0 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -616,7 +616,7 @@ sub discover_horizontal_shells { my $margin = 3 * $layerm->solid_infill_flow->scaled_width; # require at least this size my $too_narrow = diff_ex( [ map @$_, @$new_internal_solid ], - [ offset([ offset([ map @$_, @$new_internal_solid ], -$margin) ], +$margin) ], + [ offset2([ map @$_, @$new_internal_solid ], -$margin, +$margin) ], 1, ); diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index b7a1972fd6..29859b264c 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -63,6 +63,7 @@ sub model { vertices => $vertices, facets => $facets, ); + $mesh->scale_xyz($params{scale_xyz}) if $params{scale_xyz}; $mesh->scale($params{scale}) if $params{scale}; my $model = Slic3r::Model->new; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index be43be1de6..d6d5dc919d 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -301,6 +301,16 @@ sub scale { } } +sub scale_xyz { + my $self = shift; + my ($versor) = @_; + + # transform vertex coordinates + foreach my $vertex (@{$self->vertices}) { + $vertex->[$_] *= $versor->[$_] for X,Y,Z; + } +} + sub move { my $self = shift; my (@shift) = @_; diff --git a/t/shells.t b/t/shells.t index 56a3ca5950..4867dbab69 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 3; +use Test::More tests => 4; use strict; use warnings; @@ -68,4 +68,24 @@ use Slic3r::Test; "correct number of top solid shells is generated in V-shaped object"; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('perimeters', 0); + $config->set('fill_density', 0); + $config->set('cooling', 0); # prevent speed alteration + $config->set('first_layer_speed', '100%'); # prevent speed alteration + $config->set('extrusion_width', 0.2); + $config->set('bottom_solid_layers', 3); + $config->set('top_solid_layers', 0); + + my $print = Slic3r::Test::init_print('V', scale_xyz => [2,1,1], config => $config); + my %layers = (); # Z => 1 + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + $layers{$self->Z} = 1 if $info->{extruding}; + }); + is scalar(keys %layers), 3, + "shells are not extended into void if fill density is 0"; +} + __END__ From 25af3eb35fdf4710be62c5e9e5b1ad9a4c2cfd38 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Jul 2013 19:41:36 +0200 Subject: [PATCH 21/52] Proper fix for preventing shells to propagate too much --- lib/Slic3r/Print/Object.pm | 9 ++++++++- t/shells.t | 15 ++++++++++----- 2 files changed, 18 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 6ab3b40de0..9c639bce76 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -603,7 +603,14 @@ sub discover_horizontal_shells { # find intersection between neighbor and current layer's surfaces # intersections have contours and holes - my $new_internal_solid = intersection_ex( + # we update $solid so that we limit the next neighbor layer to the areas that were + # found on this one - in other words, solid shells on one layer (for a given external surface) + # are always a subset of the shells found on the previous shell layer + # this approach allows for DWIM in hollow sloping vases, where we want bottom + # shells to be generated in the base but not in the walls (where there are many + # narrow bottom surfaces): reassigning $solid will consider the 'shadow' of the + # upper perimeter as an obstacle and shell will not be propagated to more upper layers + my $new_internal_solid = $solid = intersection_ex( [ map @$_, @$solid ], [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_fill_surfaces ], undef, 1, diff --git a/t/shells.t b/t/shells.t index 4867dbab69..d38b34e954 100644 --- a/t/shells.t +++ b/t/shells.t @@ -70,21 +70,26 @@ use Slic3r::Test; { my $config = Slic3r::Config->new_from_defaults; - $config->set('perimeters', 0); + # we need to check against one perimeter because this test is calibrated + # (shape, extrusion_width) so that perimeters cover the bottom surfaces of + # their lower layer - the test checks that shells are not generated on the + # above layers (thus 'across' the shadow perimeter) + $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('cooling', 0); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('extrusion_width', 0.2); + $config->set('extrusion_width', 0.6); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); - my $print = Slic3r::Test::init_print('V', scale_xyz => [2,1,1], config => $config); + my $print = Slic3r::Test::init_print('V', config => $config); my %layers = (); # Z => 1 Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; - $layers{$self->Z} = 1 if $info->{extruding}; + $layers{$self->Z} = 1 + if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); - is scalar(keys %layers), 3, + is scalar(keys %layers), $config->bottom_solid_layers, "shells are not extended into void if fill density is 0"; } From 5c191c062fc07b858242a1081091b3aec05e9f3d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sat, 27 Jul 2013 19:43:46 +0200 Subject: [PATCH 22/52] Fix test description --- t/shells.t | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/shells.t b/t/shells.t index d38b34e954..adb177c56f 100644 --- a/t/shells.t +++ b/t/shells.t @@ -90,7 +90,7 @@ use Slic3r::Test; if $info->{extruding} && ($args->{F} // $self->F) == $config->solid_infill_speed*60; }); is scalar(keys %layers), $config->bottom_solid_layers, - "shells are not extended into void if fill density is 0"; + "shells are not propagated across perimeters of the neighbor layer"; } __END__ From 691c45d57fd1e8e0f898e0afde4528ea2760c64b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 28 Jul 2013 10:56:41 +0200 Subject: [PATCH 23/52] Smarter handling of hollow prints. Optimization included --- lib/Slic3r/Print/Object.pm | 39 +++++++++++++++++++++----------------- t/shells.t | 8 +++++++- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 9c639bce76..446a7c029a 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -581,7 +581,7 @@ sub discover_horizontal_shells { for grep $_->surface_type == S_TYPE_INTERNAL, @{$layerm->fill_surfaces}; } - foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { + EXTERNAL: foreach my $type (S_TYPE_TOP, S_TYPE_BOTTOM) { # find slices of current type for current layer # get both slices and fill_surfaces before the former contains the perimeters area # and the latter contains the enlarged external surfaces @@ -592,7 +592,7 @@ sub discover_horizontal_shells { my $solid_layers = ($type == S_TYPE_TOP) ? $Slic3r::Config->top_solid_layers : $Slic3r::Config->bottom_solid_layers; - for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; + NEIGHBOR: for (my $n = $type == S_TYPE_TOP ? $i-1 : $i+1; abs($n - $i) <= $solid_layers-1; $type == S_TYPE_TOP ? $n-- : $n++) { @@ -615,7 +615,7 @@ sub discover_horizontal_shells { [ map $_->p, grep { $_->surface_type == S_TYPE_INTERNAL || $_->surface_type == S_TYPE_INTERNALSOLID } @neighbor_fill_surfaces ], undef, 1, ); - next if !@$new_internal_solid; + next EXTERNAL if !@$new_internal_solid; # make sure the new internal solid is wide enough, as it might get collapsed when # spacing is added in Fill.pm @@ -627,21 +627,26 @@ sub discover_horizontal_shells { 1, ); - # if some parts are going to collapse, let's grow them and add the extra area to the neighbor layer - # as well as to our original surfaces so that we support this additional area in the next shell too + # if some parts are going to collapse, use a different strategy according to fill density if (@$too_narrow) { - # consider the actual fill area - my @fill_boundaries = $Slic3r::Config->fill_density > 0 - ? @neighbor_fill_surfaces - : grep $_->surface_type != S_TYPE_INTERNAL, @neighbor_fill_surfaces; - - # make sure our grown surfaces don't exceed the fill area - my @grown = map @$_, @{intersection_ex( - [ offset([ map @$_, @$too_narrow ], +$margin) ], - [ map $_->p, @fill_boundaries ], - )}; - $new_internal_solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]); - $solid = union_ex([ @grown, (map @$_, @$solid) ]); + if ($Slic3r::Config->fill_density > 0) { + # if we have internal infill, grow the collapsing parts and add the extra area to + # the neighbor layer as well as to our original surfaces so that we support this + # additional area in the next shell too + + # make sure our grown surfaces don't exceed the fill area + my @grown = map @$_, @{intersection_ex( + [ offset([ map @$_, @$too_narrow ], +$margin) ], + [ map $_->p, @neighbor_fill_surfaces ], + )}; + $new_internal_solid = $solid = union_ex([ @grown, (map @$_, @$new_internal_solid) ]); + } else { + # if we're printing a hollow object, we discard such small parts + $new_internal_solid = $solid = diff_ex( + [ map @$_, @$new_internal_solid ], + [ map @$_, @$too_narrow ], + ); + } } } diff --git a/t/shells.t b/t/shells.t index adb177c56f..d84adb3e1e 100644 --- a/t/shells.t +++ b/t/shells.t @@ -74,13 +74,19 @@ use Slic3r::Test; # (shape, extrusion_width) so that perimeters cover the bottom surfaces of # their lower layer - the test checks that shells are not generated on the # above layers (thus 'across' the shadow perimeter) + # the test is actually calibrated to leave a narrow bottom region for each + # layer - we test that in case of fill_density = 0 such narrow shells are + # discarded instead of grown $config->set('perimeters', 1); $config->set('fill_density', 0); $config->set('cooling', 0); # prevent speed alteration $config->set('first_layer_speed', '100%'); # prevent speed alteration - $config->set('extrusion_width', 0.6); + $config->set('layer_height', 0.4); + $config->set('first_layer_height', '100%'); + $config->set('extrusion_width', 0.5); $config->set('bottom_solid_layers', 3); $config->set('top_solid_layers', 0); + $config->set('solid_infill_speed', 99); my $print = Slic3r::Test::init_print('V', config => $config); my %layers = (); # Z => 1 From 91cade7e8f0b1b4c160bde4df77c1c785923632b Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 28 Jul 2013 13:39:15 +0200 Subject: [PATCH 24/52] Make sure there are no gaps in spiral vase. Includes regression test. #1251 --- lib/Slic3r/GCode.pm | 4 +++- lib/Slic3r/GCode/Layer.pm | 14 ++++++++++---- lib/Slic3r/GCode/SpiralVase.pm | 19 +++++++++++++++---- t/shells.t | 25 ++++++++++++++++++++++++- 4 files changed, 52 insertions(+), 10 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 71a9afbe86..d2710af433 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -10,6 +10,7 @@ use Slic3r::Surface ':types'; has 'config' => (is => 'ro', required => 1); has 'extruders' => (is => 'ro', default => sub {0}, required => 1); has 'multiple_extruders' => (is => 'lazy'); +has 'enable_loop_clipping' => (is => 'rw', default => sub {1}); has 'enable_wipe' => (is => 'lazy'); # at least one extruder has wipe enabled has 'layer_count' => (is => 'ro', required => 1 ); has 'layer' => (is => 'rw'); @@ -195,7 +196,8 @@ sub extrude_loop { # clip the path to avoid the extruder to get exactly on the first point of the loop; # if polyline was shorter than the clipping distance we'd get a null polyline, so # we discard it in that case - $extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING); + $extrusion_path->clip_end(scale $extrusion_path->flow_spacing * &Slic3r::LOOP_CLIPPING_LENGTH_OVER_SPACING) + if $self->enable_loop_clipping; return '' if !@{$extrusion_path->polyline}; my @paths = (); diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index 26db9fc067..df3e85b945 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -27,6 +27,15 @@ sub process_layer { my ($layer, $object_copies) = @_; my $gcode = ""; + # check whether we're going to apply spiralvase logic + my $spiralvase = defined $self->spiralvase + && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) + && ($layer->id >= $Slic3r::Config->skirt_height) + && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + + # if we're going to apply spiralvase to this layer, disable loop clipping + $self->gcodegen->enable_loop_clipping(!$spiralvase); + if (!$self->second_layer_things_done && $layer->id == 1) { for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->temperature}) { $gcode .= $self->gcodegen->set_temperature($self->extruders->[$t]->temperature, 0, $t) @@ -167,10 +176,7 @@ sub process_layer { # apply spiral vase post-processing if this layer contains suitable geometry $gcode = $self->spiralvase->process_layer($gcode, $layer) - if defined $self->spiralvase - && ($layer->id > 0 || $Slic3r::Config->brim_width == 0) - && ($layer->id >= $Slic3r::Config->skirt_height) - && ($layer->id >= $Slic3r::Config->bottom_solid_layers); + if $spiralvase; return $gcode; } diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm index 5c0a50fd3d..dc67aafc44 100644 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -17,6 +17,7 @@ sub process_layer { my $new_gcode = ""; my $layer_height = $layer->height; my $z = unscale($layer->print_z) - $layer_height; + my $newlayer = 0; Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { my ($reader, $cmd, $args, $info) = @_; @@ -24,11 +25,21 @@ sub process_layer { my $line = $info->{raw}; $line =~ s/Z([^ ]+)/Z$z/; $new_gcode .= "$line\n"; - } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{extruding} && $info->{dist_XY}) { - $z += $info->{dist_XY} * $layer_height / $total_layer_length; + $newlayer = 1; + } elsif ($cmd eq 'G1' && !exists $args->{Z} && $info->{dist_XY}) { my $line = $info->{raw}; - $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; - $new_gcode .= "$line\n"; + if ($info->{extruding}) { + $z += $info->{dist_XY} * $layer_height / $total_layer_length; + $line =~ s/^G1 /sprintf 'G1 Z%.3f ', $z/e; + $new_gcode .= "$line\n"; + } elsif ($newlayer) { + # remove the first travel move after layer change; extrusion + # will just blend to the first loop vertex + # TODO: should we adjust (stretch) E for the first loop segment? + $newlayer = 0; + } else { + $new_gcode .= "$line\n"; + } } else { $new_gcode .= "$info->{raw}\n"; } diff --git a/t/shells.t b/t/shells.t index d84adb3e1e..0da0099ea9 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 4; +use Test::More tests => 5; use strict; use warnings; @@ -99,4 +99,27 @@ use Slic3r::Test; "shells are not propagated across perimeters of the neighbor layer"; } +{ + my $config = Slic3r::Config->new_from_defaults; + $config->set('spiral_vase', 1); + $config->set('bottom_solid_layers', 0); + $config->set('skirts', 0); + + # TODO: this needs to be tested with a model with sloping edges, where starting + # points of each layer are not aligned - in that case we would test that no + # travel moves are left to move to the new starting point - in a cube, end + # points coincide with next layer starting points (provided there's no clipping) + my $print = Slic3r::Test::init_print('20mm_cube', config => $config); + my $travel_moves_after_first_extrusion = 0; + my $started_extruding = 0; + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + $started_extruding = 1 if $info->{extruding}; + $travel_moves_after_first_extrusion++ + if $info->{travel} && $started_extruding && !exists $args->{Z}; + }); + is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase"; +} + __END__ From 3bcb2f04ed313205e6a4bcddfed80325b60a152c Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Sun, 28 Jul 2013 15:02:03 +0200 Subject: [PATCH 25/52] Add spiral vase test for hollow models --- lib/Slic3r/Test.pm | 7 +++++++ t/shells.t | 30 ++++++++++++++++++------------ 2 files changed, 25 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/Test.pm b/lib/Slic3r/Test.pm index 29859b264c..b1b619aaba 100644 --- a/lib/Slic3r/Test.pm +++ b/lib/Slic3r/Test.pm @@ -55,6 +55,13 @@ sub model { $facets = [ [0,1,2],[2,3,4],[2,5,0],[4,6,2],[2,6,5],[2,1,3],[7,8,9],[10,9,8],[11,9,10],[12,9,11],[9,13,14],[7,15,16],[10,17,0],[10,0,5],[12,11,6],[18,16,0],[6,19,13],[6,13,9],[9,12,6],[17,18,0],[11,10,5],[11,5,6],[14,16,15],[17,7,18],[16,18,7],[14,15,9],[7,9,15],[7,17,8],[10,8,17],[20,21,22],[23,24,25],[26,23,27],[28,27,23],[29,28,23],[30,29,23],[25,31,32],[22,33,34],[35,36,37],[24,38,39],[21,40,41],[38,42,20],[33,43,44],[6,4,23],[6,23,25],[36,35,1],[1,0,38],[1,38,36],[29,30,4],[25,32,6],[40,42,0],[35,45,1],[4,3,28],[4,28,29],[3,1,45],[3,45,28],[22,34,19],[19,6,32],[19,32,22],[42,38,0],[30,23,4],[0,16,43],[0,43,40],[24,37,36],[38,24,36],[24,23,37],[37,23,26],[22,32,20],[20,32,31],[33,41,40],[43,33,40],[45,35,26],[37,26,35],[33,44,34],[44,43,46],[20,42,21],[40,21,42],[31,39,38],[20,31,38],[33,22,41],[21,41,22],[31,25,39],[24,39,25],[26,27,45],[28,45,27],[47,48,49],[47,50,48],[51,48,50],[52,48,51],[53,48,52],[54,55,56],[57,55,54],[58,55,57],[49,59,47],[60,56,55],[59,56,60],[60,47,59],[48,53,16],[56,13,19],[54,56,19],[56,59,13],[59,49,14],[59,14,13],[49,48,16],[49,16,14],[44,46,60],[44,60,55],[51,50,43],[19,34,58],[19,58,57],[53,52,16],[43,16,52],[43,52,51],[57,54,19],[47,60,46],[55,58,34],[55,34,44],[50,47,46],[50,46,43] ], + } elsif ($model_name eq '40x10') { + $vertices = [ + [12.8680295944214,29.5799007415771,12],[11.7364797592163,29.8480796813965,12],[11.1571502685547,29.5300102233887,12],[10.5814504623413,29.9830799102783,12],[10,29.6000003814697,12],[9.41855144500732,29.9830799102783,12],[8.84284687042236,29.5300102233887,12],[8.26351833343506,29.8480796813965,12],[7.70256900787354,29.3210391998291,12],[7.13196802139282,29.5799007415771,12],[6.59579277038574,28.9761600494385,12],[6.03920221328735,29.1821594238281,12],[5.53865718841553,28.5003795623779,12],[5,28.6602592468262,12],[4.54657793045044,27.9006500244141,12],[4.02841377258301,28.0212306976318,12],[3.63402199745178,27.1856994628906,12],[3.13758301734924,27.2737407684326,12],[2.81429696083069,26.3659801483154,12],[2.33955597877502,26.4278793334961,12],[2.0993549823761,25.4534206390381,12],[1.64512205123901,25.4950904846191,12],[1.49962198734283,24.4613399505615,12],[1.0636739730835,24.4879894256592,12],[1.02384400367737,23.4042091369629,12],[0.603073298931122,23.4202003479004,12],[0.678958415985107,22.2974300384521,12],[0.269550800323486,22.3061599731445,12],[0.469994693994522,21.1571502685547,12],[0.067615881562233,21.1609306335449,12],[0.399999290704727,20,12],[0,20,12],[0.399999290704727,5,12],[0,5,12],[0.456633001565933,4.2804012298584,12],[0.0615576282143593,4.21782684326172,12],[0.625140011310577,3.5785219669342,12],[0.244717106223106,3.45491504669189,12],[0.901369392871857,2.91164398193359,12],[0.544967114925385,2.73004698753357,12],[1.27852201461792,2.29618692398071,12],[0.954914808273315,2.06107401847839,12],[1.74730801582336,1.74730801582336,12],[1.46446597576141,1.46446597576141,12],[2.29618692398071,1.27852201461792,12],[2.06107401847839,0.954914808273315,12],[2.91164398193359,0.901369392871857,12],[2.73004698753357,0.544967114925385,12],[3.5785219669342,0.625140011310577,12],[3.45491504669189,0.244717106223106,12],[4.2804012298584,0.456633001565933,12],[4.21782684326172,0.0615576282143593,12],[5,0.399999290704727,12],[5,0,12],[19.6000003814697,0.399999290704727,12],[20,0,12],[19.6000003814697,20,12],[20,20,12],[19.5300102233887,21.1571502685547,12],[19.9323806762695,21.1609306335449,12],[19.3210391998291,22.2974300384521,12],[19.7304496765137,22.3061599731445,12],[18.9761600494385,23.4042091369629,12],[19.3969306945801,23.4202003479004,12],[18.5003795623779,24.4613399505615,12],[18.9363307952881,24.4879894256592,12],[17.9006500244141,25.4534206390381,12],[18.3548793792725,25.4950904846191,12],[17.1856994628906,26.3659801483154,12],[17.6604404449463,26.4278793334961,12],[16.3659801483154,27.1856994628906,12],[16.862419128418,27.2737407684326,12],[15.4534196853638,27.9006500244141,12],[15.9715900421143,28.0212306976318,12],[14.4613399505615,28.5003795623779,12],[15,28.6602592468262,12],[13.4042100906372,28.9761600494385,12],[13.9608001708984,29.1821594238281,12],[12.2974300384521,29.3210391998291,12],[7.13196802139282,29.5799007415771,0],[8.26351833343506,29.8480796813965,0],[8.84284687042236,29.5300102233887,0],[9.41855144500732,29.9830799102783,0],[10,29.6000003814697,0],[10.5814504623413,29.9830799102783,0],[11.1571502685547,29.5300102233887,0],[11.7364797592163,29.8480796813965,0],[12.2974300384521,29.3210391998291,0],[12.8680295944214,29.5799007415771,0],[13.4042100906372,28.9761600494385,0],[13.9608001708984,29.1821594238281,0],[14.4613399505615,28.5003795623779,0],[15,28.6602592468262,0],[15.4534196853638,27.9006500244141,0],[15.9715900421143,28.0212306976318,0],[16.3659801483154,27.1856994628906,0],[16.862419128418,27.2737407684326,0],[17.1856994628906,26.3659801483154,0],[17.6604404449463,26.4278793334961,0],[17.9006500244141,25.4534206390381,0],[18.3548793792725,25.4950904846191,0],[18.5003795623779,24.4613399505615,0],[18.9363307952881,24.4879894256592,0],[18.9761600494385,23.4042091369629,0],[19.3969306945801,23.4202003479004,0],[19.3210391998291,22.2974300384521,0],[19.7304496765137,22.3061599731445,0],[19.5300102233887,21.1571502685547,0],[19.9323806762695,21.1609306335449,0],[19.6000003814697,20,0],[20,20,0],[19.6000003814697,0.399999290704727,0],[20,0,0],[5,0.399999290704727,0],[5,0,0],[4.2804012298584,0.456633001565933,0],[4.21782684326172,0.0615576282143593,0],[3.5785219669342,0.625140011310577,0],[3.45491504669189,0.244717106223106,0],[2.91164398193359,0.901369392871857,0],[2.73004698753357,0.544967114925385,0],[2.29618692398071,1.27852201461792,0],[2.06107401847839,0.954914808273315,0],[1.74730801582336,1.74730801582336,0],[1.46446597576141,1.46446597576141,0],[1.27852201461792,2.29618692398071,0],[0.954914808273315,2.06107401847839,0],[0.901369392871857,2.91164398193359,0],[0.544967114925385,2.73004698753357,0],[0.625140011310577,3.5785219669342,0],[0.244717106223106,3.45491504669189,0],[0.456633001565933,4.2804012298584,0],[0.0615576282143593,4.21782684326172,0],[0.399999290704727,5,0],[0,5,0],[0.399999290704727,20,0],[0,20,0],[0.469994693994522,21.1571502685547,0],[0.067615881562233,21.1609306335449,0],[0.678958415985107,22.2974300384521,0],[0.269550800323486,22.3061599731445,0],[1.02384400367737,23.4042091369629,0],[0.603073298931122,23.4202003479004,0],[1.49962198734283,24.4613399505615,0],[1.0636739730835,24.4879894256592,0],[2.0993549823761,25.4534206390381,0],[1.64512205123901,25.4950904846191,0],[2.81429696083069,26.3659801483154,0],[2.33955597877502,26.4278793334961,0],[3.63402199745178,27.1856994628906,0],[3.13758301734924,27.2737407684326,0],[4.54657793045044,27.9006500244141,0],[4.02841377258301,28.0212306976318,0],[5.53865718841553,28.5003795623779,0],[5,28.6602592468262,0],[6.59579277038574,28.9761600494385,0],[6.03920221328735,29.1821594238281,0],[7.70256900787354,29.3210391998291,0] + ]; + $facets = [ + [0,1,2],[2,1,3],[2,3,4],[4,3,5],[4,5,6],[6,5,7],[6,7,8],[8,7,9],[8,9,10],[10,9,11],[10,11,12],[12,11,13],[12,13,14],[14,13,15],[14,15,16],[16,15,17],[16,17,18],[18,17,19],[18,19,20],[20,19,21],[20,21,22],[22,21,23],[22,23,24],[24,23,25],[24,25,26],[26,25,27],[26,27,28],[28,27,29],[28,29,30],[30,29,31],[30,31,32],[32,31,33],[32,33,34],[34,33,35],[34,35,36],[36,35,37],[36,37,38],[38,37,39],[38,39,40],[40,39,41],[40,41,42],[42,41,43],[42,43,44],[44,43,45],[44,45,46],[46,45,47],[46,47,48],[48,47,49],[48,49,50],[50,49,51],[50,51,52],[52,51,53],[52,53,54],[54,53,55],[54,55,56],[56,55,57],[56,57,58],[58,57,59],[58,59,60],[60,59,61],[60,61,62],[62,61,63],[62,63,64],[64,63,65],[64,65,66],[66,65,67],[66,67,68],[68,67,69],[68,69,70],[70,69,71],[70,71,72],[72,71,73],[72,73,74],[74,73,75],[74,75,76],[76,75,77],[76,77,78],[78,77,0],[78,0,2],[79,80,81],[81,80,82],[81,82,83],[83,82,84],[83,84,85],[85,84,86],[85,86,87],[87,86,88],[87,88,89],[89,88,90],[89,90,91],[91,90,92],[91,92,93],[93,92,94],[93,94,95],[95,94,96],[95,96,97],[97,96,98],[97,98,99],[99,98,100],[99,100,101],[101,100,102],[101,102,103],[103,102,104],[103,104,105],[105,104,106],[105,106,107],[107,106,108],[107,108,109],[109,108,110],[109,110,111],[111,110,112],[111,112,113],[113,112,114],[113,114,115],[115,114,116],[115,116,117],[117,116,118],[117,118,119],[119,118,120],[119,120,121],[121,120,122],[121,122,123],[123,122,124],[123,124,125],[125,124,126],[125,126,127],[127,126,128],[127,128,129],[129,128,130],[129,130,131],[131,130,132],[131,132,133],[133,132,134],[133,134,135],[135,134,136],[135,136,137],[137,136,138],[137,138,139],[139,138,140],[139,140,141],[141,140,142],[141,142,143],[143,142,144],[143,144,145],[145,144,146],[145,146,147],[147,146,148],[147,148,149],[149,148,150],[149,150,151],[151,150,152],[151,152,153],[153,152,154],[153,154,155],[155,154,156],[155,156,157],[157,156,79],[157,79,81],[57,110,108],[57,108,59],[59,108,106],[59,106,61],[61,106,104],[61,104,63],[63,104,102],[63,102,65],[65,102,100],[65,100,67],[67,100,98],[67,98,69],[69,98,96],[69,96,71],[71,96,94],[71,94,73],[73,94,92],[73,92,75],[75,92,90],[75,90,77],[77,90,88],[77,88,0],[0,88,86],[0,86,1],[1,86,84],[1,84,3],[3,84,82],[3,82,5],[5,82,80],[5,80,7],[7,80,79],[7,79,9],[9,79,156],[9,156,11],[11,156,154],[11,154,13],[13,154,152],[13,152,15],[15,152,150],[15,150,17],[17,150,148],[17,148,19],[19,148,146],[19,146,21],[21,146,144],[21,144,23],[23,144,142],[23,142,25],[25,142,140],[25,140,27],[27,140,138],[27,138,29],[29,138,136],[29,136,31],[33,31,134],[134,31,136],[33,134,132],[33,132,35],[35,132,130],[35,130,37],[37,130,128],[37,128,39],[39,128,126],[39,126,41],[41,126,124],[41,124,43],[43,124,122],[43,122,45],[45,122,120],[45,120,47],[47,120,118],[47,118,49],[49,118,116],[49,116,51],[51,116,114],[51,114,53],[55,53,112],[112,53,114],[57,55,110],[110,55,112],[30,135,137],[30,137,28],[28,137,139],[28,139,26],[26,139,141],[26,141,24],[24,141,143],[24,143,22],[22,143,145],[22,145,20],[20,145,147],[20,147,18],[18,147,149],[18,149,16],[16,149,151],[16,151,14],[14,151,153],[14,153,12],[12,153,155],[12,155,10],[10,155,157],[10,157,8],[8,157,81],[8,81,6],[6,81,83],[6,83,4],[4,83,85],[4,85,2],[2,85,87],[2,87,78],[78,87,89],[78,89,76],[76,89,91],[76,91,74],[74,91,93],[74,93,72],[72,93,95],[72,95,70],[70,95,97],[70,97,68],[68,97,99],[68,99,66],[66,99,101],[66,101,64],[64,101,103],[64,103,62],[62,103,105],[62,105,60],[60,105,107],[60,107,58],[58,107,109],[58,109,56],[30,32,135],[135,32,133],[52,113,115],[52,115,50],[50,115,117],[50,117,48],[48,117,119],[48,119,46],[46,119,121],[46,121,44],[44,121,123],[44,123,42],[42,123,125],[42,125,40],[40,125,127],[40,127,38],[38,127,129],[38,129,36],[36,129,131],[36,131,34],[34,131,133],[34,133,32],[52,54,113],[113,54,111],[54,56,111],[111,56,109] + ], } else { return undef; } diff --git a/t/shells.t b/t/shells.t index 0da0099ea9..8cfcc87f69 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 5; +use Test::More tests => 6; use strict; use warnings; @@ -109,17 +109,23 @@ use Slic3r::Test; # points of each layer are not aligned - in that case we would test that no # travel moves are left to move to the new starting point - in a cube, end # points coincide with next layer starting points (provided there's no clipping) - my $print = Slic3r::Test::init_print('20mm_cube', config => $config); - my $travel_moves_after_first_extrusion = 0; - my $started_extruding = 0; - Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { - my ($self, $cmd, $args, $info) = @_; - - $started_extruding = 1 if $info->{extruding}; - $travel_moves_after_first_extrusion++ - if $info->{travel} && $started_extruding && !exists $args->{Z}; - }); - is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase"; + my $test = sub { + my ($model_name, $description) = @_; + my $print = Slic3r::Test::init_print($model_name, config => $config); + my $travel_moves_after_first_extrusion = 0; + my $started_extruding = 0; + Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { + my ($self, $cmd, $args, $info) = @_; + + $started_extruding = 1 if $info->{extruding}; + $travel_moves_after_first_extrusion++ + if $info->{travel} && $started_extruding && !exists $args->{Z}; + }); + is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase ($description)"; + }; + + $test->('20mm_cube', 'solid model'); + $test->('40x10', 'hollow model'); } __END__ From 51de3ce14fe3630f1337ae95efe93d1e22e28b2f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 00:27:53 +0200 Subject: [PATCH 26/52] Switch print_z to unscaled coordinates --- lib/Slic3r/GCode.pm | 3 +-- lib/Slic3r/GCode/SpiralVase.pm | 2 +- lib/Slic3r/Layer.pm | 4 ++-- lib/Slic3r/Print/Object.pm | 6 +++--- lib/Slic3r/Test/SectionCut.pm | 8 ++++---- 5 files changed, 11 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index d2710af433..e8b7e6dd02 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -119,12 +119,11 @@ sub change_layer { return $gcode; } -# this method accepts Z in scaled coordinates +# this method accepts Z in unscaled coordinates sub move_z { my $self = shift; my ($z, $comment) = @_; - $z *= &Slic3r::SCALING_FACTOR; $z += $self->config->z_offset; my $gcode = ""; diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm index dc67aafc44..1c94b251a9 100644 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -16,7 +16,7 @@ sub process_layer { my $new_gcode = ""; my $layer_height = $layer->height; - my $z = unscale($layer->print_z) - $layer_height; + my $z = $layer->print_z - $layer_height; my $newlayer = 0; Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { my ($reader, $cmd, $args, $info) = @_; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index 77dd4ad908..ece5062532 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -11,7 +11,7 @@ has 'regions' => (is => 'ro', default => sub { [] }); has 'slicing_errors' => (is => 'rw'); has 'slice_z' => (is => 'ro', required => 1); # Z used for slicing in scaled coordinates -has 'print_z' => (is => 'ro', required => 1); # Z used for printing in scaled coordinates +has 'print_z' => (is => 'ro', required => 1); # Z used for printing in unscaled coordinates has 'height' => (is => 'ro', required => 1); # layer height in unscaled coordinates # collection of expolygons generated by slicing the original geometry; @@ -51,7 +51,7 @@ sub support_material_contact_height { # Z used for printing support material contact in scaled coordinates sub support_material_contact_z { my $self = shift; - return $self->print_z - ($self->height - $self->support_material_contact_height) / &Slic3r::SCALING_FACTOR; + return ($self->print_z - ($self->height - $self->support_material_contact_height)) / &Slic3r::SCALING_FACTOR; } sub upper_layer_slices { diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 446a7c029a..8bd80d1518 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -36,7 +36,7 @@ sub BUILD { object => $self, id => $id, height => $height, - print_z => scale $print_z, + print_z => $print_z, slice_z => -1, ); } @@ -71,7 +71,7 @@ sub BUILD { object => $self, id => $id, height => $height, - print_z => scale $print_z, + print_z => $print_z, slice_z => scale $slice_z, ); @@ -904,7 +904,7 @@ sub generate_support_material { if ($Slic3r::debug) { printf "Layer %d (z = %.2f) has %d generic support areas, %d normal interface areas, %d contact areas\n", - $i, unscale($layer->print_z), scalar(@{$layers{$i}}), scalar(@{$layers_interfaces{$i}}), scalar(@{$layers_contact_areas{$i}}); + $i, $layer->print_z, scalar(@{$layers{$i}}), scalar(@{$layers_interfaces{$i}}), scalar(@{$layers_contact_areas{$i}}); } } } diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index b2403ef399..97d76af8b6 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -25,7 +25,7 @@ sub export_svg { my ($filename) = @_; my $print_size = $self->print->size; - $self->height(unscale max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); + $self->height(max(map $_->print_z, map @{$_->layers}, @{$self->print->objects})); my $svg = SVG->new( width => $self->scale * unscale($print_size->[X]), height => $self->scale * $self->height, @@ -106,7 +106,7 @@ sub _plot { # we're cutting the path in the longitudinal direction, so we've got a rectangle push @rectangles, { 'x' => $self->scale * unscale $line->[A][X], - 'y' => $self->scale * $self->_y(unscale($layer->print_z)), + 'y' => $self->scale * $self->_y($layer->print_z), 'width' => $self->scale * $width, 'height' => $self->scale * $radius * 2, 'rx' => $self->scale * $radius * 0.35, @@ -115,7 +115,7 @@ sub _plot { } else { push @circles, { 'cx' => $self->scale * (unscale($line->[A][X]) + $radius), - 'cy' => $self->scale * $self->_y(unscale($layer->print_z) - $radius), + 'cy' => $self->scale * $self->_y($layer->print_z - $radius), 'r' => $self->scale * $radius, }; } @@ -125,7 +125,7 @@ sub _plot { my $height = $path->height // $layer->height; { 'x' => $self->scale * unscale $_->[A][X], - 'y' => $self->scale * $self->_y(unscale($layer->print_z)), + 'y' => $self->scale * $self->_y($layer->print_z), 'width' => $self->scale * unscale(abs($_->[B][X] - $_->[A][X])), 'height' => $self->scale * $height, 'rx' => $self->scale * $height * 0.35, From 948b43fe0d820c4f1b97443c28a55803c81c8724 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 11:05:04 +0200 Subject: [PATCH 27/52] Don't try to align rectilinear infill if solid, otherwise there will be a gap at one side --- lib/Slic3r/ExPolygon.pm | 8 ++++ lib/Slic3r/Fill.pm | 1 - lib/Slic3r/Fill/Rectilinear.pm | 87 +++++++++++++++------------------- t/fill.t | 27 ++++++++++- 4 files changed, 73 insertions(+), 50 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 610751a629..bd5ed4e3fb 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -173,6 +173,14 @@ sub translate { $self; } +sub align_to_origin { + my $self = shift; + + my $bb = $self->bounding_box; + $self->translate(-$bb->x_min, -$bb->y_min); + $self; +} + sub rotate { my $self = shift; $_->rotate(@_) for @$self; diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 5270c7250d..143dc905b2 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -165,7 +165,6 @@ sub make_fill { $surface, density => $density, flow_spacing => $flow_spacing, - dont_adjust => $is_bridge, ); } my $params = shift @paths; diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index 08bbee21fc..c58b3871a0 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -5,7 +5,7 @@ extends 'Slic3r::Fill::Base'; has 'cache' => (is => 'rw', default => sub {{}}); -use Slic3r::Geometry qw(A B X Y scale unscale scaled_epsilon); +use Slic3r::Geometry qw(A B X Y MIN scale unscale scaled_epsilon); sub fill_surface { my $self = shift; @@ -16,72 +16,63 @@ sub fill_surface { my $rotate_vector = $self->infill_direction($surface); $self->rotate_points($expolygon, $rotate_vector); - my ($expolygon_off) = $expolygon->offset_ex(scale $params{flow_spacing}/2); - return {} if !$expolygon_off; # skip some very small polygons (which shouldn't arrive here) + my $flow_spacing = $params{flow_spacing}; + my $min_spacing = scale $params{flow_spacing}; + my $line_spacing = $min_spacing / $params{density}; + my $line_oscillation = $line_spacing - $min_spacing; + my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); + my $bounding_box = $expolygon->bounding_box; - my $flow_spacing = $params{flow_spacing}; - my $min_spacing = scale $params{flow_spacing}; - my $distance_between_lines = $min_spacing / $params{density}; - my $line_oscillation = $distance_between_lines - $min_spacing; - my $is_line_pattern = $self->isa('Slic3r::Fill::Line'); + # define flow spacing according to requested density + if ($params{density} == 1 && !$params{dont_adjust}) { + $line_spacing = $self->adjust_solid_spacing( + width => $bounding_box->size->[X], + distance => $line_spacing, + ); + $flow_spacing = unscale $line_spacing; + } else { + # extend bounding box so that our pattern will be aligned with other layers + # $bounding_box->[X1] and [Y1] represent the displacement between new bounding box offset and old one + $bounding_box->extents->[X][MIN] -= $bounding_box->x_min; + $bounding_box->extents->[Y][MIN] -= $bounding_box->y_min; + } - my $cache_id = sprintf "d%s_s%.2f_a%.2f", - $params{density}, $params{flow_spacing}, $rotate_vector->[0][0]; - - if (!$self->cache->{$cache_id}) { - # compute bounding box - my $bounding_box; - { - my $bb_polygon = $self->bounding_box->polygon; - $bb_polygon->scale(sqrt 2); - $self->rotate_points($bb_polygon, $rotate_vector); - $bounding_box = $bb_polygon->bounding_box; + # generate the basic pattern + my $i = 0; + my $x = $bounding_box->x_min; + my $x_max = $bounding_box->x_max + scaled_epsilon; + my @vertical_lines = (); + while ($x <= $x_max) { + my $vertical_line = Slic3r::Line->new([$x, $bounding_box->y_max], [$x, $bounding_box->y_min]); + if ($is_line_pattern && $i % 2) { + $vertical_line->[A][X] += $line_oscillation; + $vertical_line->[B][X] -= $line_oscillation; } - - # define flow spacing according to requested density - if ($params{density} == 1 && !$params{dont_adjust}) { - $distance_between_lines = $self->adjust_solid_spacing( - width => $bounding_box->size->[X], - distance => $distance_between_lines, - ); - $flow_spacing = unscale $distance_between_lines; - } - - # generate the basic pattern - my $x = $bounding_box->x_min; - my @vertical_lines = (); - for (my $i = 0; $x <= $bounding_box->x_max + scaled_epsilon; $i++) { - my $vertical_line = Slic3r::Line->new([$x, $bounding_box->y_max], [$x, $bounding_box->y_min]); - if ($is_line_pattern && $i % 2) { - $vertical_line->[A][X] += $line_oscillation; - $vertical_line->[B][X] -= $line_oscillation; - } - push @vertical_lines, $vertical_line; - $x += $distance_between_lines; - } - - $self->cache->{$cache_id} = [@vertical_lines]; + push @vertical_lines, $vertical_line; + $i++; + $x += $line_spacing; } # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides - my @paths = @{ Boost::Geometry::Utils::polygon_multi_linestring_intersection( - +($expolygon->offset_ex(scaled_epsilon))[0], # TODO: we should use all the resulting expolygons and clip the linestrings to a multipolygon object - [ @{ $self->cache->{$cache_id} } ], + my @paths = @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection( + [ $expolygon->offset_ex(scaled_epsilon) ], + [ @vertical_lines ], ) }; # connect lines unless ($params{dont_connect}) { + my ($expolygon_off) = $expolygon->offset_ex(scale $params{flow_spacing}/2); my $collection = Slic3r::Polyline::Collection->new( polylines => [ map Slic3r::Polyline->new(@$_), @paths ], ); @paths = (); my $tolerance = 10 * scaled_epsilon; - my $diagonal_distance = $distance_between_lines * 2; + my $diagonal_distance = $line_spacing * 2; my $can_connect = $is_line_pattern ? sub { - ($_[X] >= ($distance_between_lines - $line_oscillation) - $tolerance) && ($_[X] <= ($distance_between_lines + $line_oscillation) + $tolerance) + ($_[X] >= ($line_spacing - $line_oscillation) - $tolerance) && ($_[X] <= ($line_spacing + $line_oscillation) + $tolerance) && $_[Y] <= $diagonal_distance } : sub { $_[X] <= $diagonal_distance && $_[Y] <= $diagonal_distance }; diff --git a/t/fill.t b/t/fill.t index 28c2509bd5..9c7cbabe92 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 10; +plan tests => 11; BEGIN { use FindBin; @@ -11,6 +11,7 @@ BEGIN { use Slic3r; use Slic3r::Geometry qw(scale X Y); +use Slic3r::Geometry::Clipper qw(diff_ex); use Slic3r::Surface qw(:types); use Slic3r::Test; @@ -48,6 +49,30 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } } } +{ + my $expolygon = Slic3r::ExPolygon->new([ + [6883102, 9598327.01296997], + [6883102, 20327272.01297], + [3116896, 20327272.01297], + [3116896, 9598327.01296997], + ]); + $expolygon->align_to_origin; + my $filler = Slic3r::Fill::Rectilinear->new( + bounding_box => $expolygon->bounding_box, + angle => 0, + ); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_BOTTOM, + expolygon => $expolygon, + ); + my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.55, density => 1); + + # check whether any part was left uncovered + my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths; + my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ]); + is scalar(@$uncovered), 0, 'solid surface is fully filled'; +} + { my $collection = Slic3r::Polyline::Collection->new(polylines => [ Slic3r::Polyline->new([0,15], [0,18], [0,20]), From 1f36406a6285fabf63904f7b9d43087081cffaa1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 11:54:32 +0200 Subject: [PATCH 28/52] Bugfix: thin bridge anchors were ignored. #304 --- lib/Slic3r/Layer/Region.pm | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 653931d8d4..877f64cae6 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -419,8 +419,10 @@ sub process_external_surfaces { # detect bridge direction before merging grown surfaces otherwise adjacent bridges # would get merged into a single one while they need different directions + # also, supply the original expolygon instead of the grown one, because in case + # of very thin (but still working) anchors, the grown expolygon would go beyond them my $angle = $self->id > 0 - ? $self->_detect_bridge_direction($grown) + ? $self->_detect_bridge_direction($surface->expolygon) : undef; push @bottom, $surface->clone(expolygon => $grown, bridge_angle => $angle); @@ -470,6 +472,7 @@ sub _detect_bridge_direction { my $self = shift; my ($expolygon) = @_; + my ($grown) = $expolygon->offset_ex(+$self->perimeter_flow->scaled_width); my @lower = @{$self->layer->object->layers->[ $self->id - 1 ]->slices}; # expolygons # detect what edges lie on lower slices @@ -477,7 +480,7 @@ sub _detect_bridge_direction { foreach my $lower (@lower) { # turn bridge contour and holes into polylines and then clip them # with each lower slice's contour - my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @$expolygon; + my @clipped = map $_->split_at_first_point->clip_with_polygon($lower->contour), @$grown; if (@clipped == 2) { # If the split_at_first_point() call above happens to split the polygon inside the clipping area # we would get two consecutive polylines instead of a single one, so we use this ugly hack to From 2a2d15e4223ba94fa0e0d5bcfce17ffcc3606ebd Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 12:15:30 +0200 Subject: [PATCH 29/52] Fix error after recent change about bridges --- lib/Slic3r/Fill.pm | 18 +++++++----------- lib/Slic3r/Layer/Region.pm | 3 ++- 2 files changed, 9 insertions(+), 12 deletions(-) diff --git a/lib/Slic3r/Fill.pm b/lib/Slic3r/Fill.pm index 143dc905b2..5da06753e3 100644 --- a/lib/Slic3r/Fill.pm +++ b/lib/Slic3r/Fill.pm @@ -157,17 +157,13 @@ sub make_fill { next SURFACE unless $density > 0; } - my @paths; - { - my $f = $self->filler($filler); - $f->layer_id($layerm->id); - @paths = $f->fill_surface( - $surface, - density => $density, - flow_spacing => $flow_spacing, - ); - } - my $params = shift @paths; + my $f = $self->filler($filler); + $f->layer_id($layerm->id); + my ($params, @paths) = $f->fill_surface( + $surface, + density => $density, + flow_spacing => $flow_spacing, + ); # ugly hack(tm) to get the right amount of flow (GCode.pm should be fixed) $params->{flow_spacing} = $layerm->extruders->{infill}->bridge_flow->width if $is_bridge; diff --git a/lib/Slic3r/Layer/Region.pm b/lib/Slic3r/Layer/Region.pm index 877f64cae6..93574e26d8 100644 --- a/lib/Slic3r/Layer/Region.pm +++ b/lib/Slic3r/Layer/Region.pm @@ -531,7 +531,7 @@ sub _detect_bridge_direction { # detect anchors as intersection between our bridge expolygon and the lower slices my $anchors = intersection_ex( - [ @$expolygon ], + [ @$grown ], [ map @$_, @lower ], undef, 1, # safety offset required to avoid Clipper from detecting empty intersection while Boost actually found some @edges @@ -549,6 +549,7 @@ sub _detect_bridge_direction { # generate lines in this direction my $bounding_box = Slic3r::Geometry::BoundingBox->new_from_points([ map @$_, map @$_, @$anchors ]); + my @lines = (); for (my $x = $bounding_box->x_min; $x <= $bounding_box->x_max; $x += $line_increment) { push @lines, [ [$x, $bounding_box->y_min], [$x, $bounding_box->y_max] ]; From 99963775ffcb53edada8e8f1dff46035aadb72e1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 12:28:23 +0200 Subject: [PATCH 30/52] Extend tests about solid infill adjustment --- t/fill.t | 49 ++++++++++++++++++++++++++++++++++--------------- 1 file changed, 34 insertions(+), 15 deletions(-) diff --git a/t/fill.t b/t/fill.t index 9c7cbabe92..9ffce0ba18 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 11; +plan tests => 31; BEGIN { use FindBin; @@ -50,27 +50,46 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } } { + my $test = sub { + my ($expolygon) = @_; + $expolygon->align_to_origin; + my $filler = Slic3r::Fill::Rectilinear->new( + bounding_box => $expolygon->bounding_box, + angle => 0, + ); + my $surface = Slic3r::Surface->new( + surface_type => S_TYPE_BOTTOM, + expolygon => $expolygon, + ); + my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.55, density => 1); + + # check whether any part was left uncovered + my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths; + my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); + is scalar(@$uncovered), 0, 'solid surface is fully filled'; + if (0 && @$uncovered) { + require "Slic3r/SVG.pm"; + Slic3r::SVG::output( + "uncovered.svg", + expolygons => [$expolygon], + red_expolygons => $uncovered, + ); + exit; + } + }; + my $expolygon = Slic3r::ExPolygon->new([ [6883102, 9598327.01296997], [6883102, 20327272.01297], [3116896, 20327272.01297], [3116896, 9598327.01296997], ]); - $expolygon->align_to_origin; - my $filler = Slic3r::Fill::Rectilinear->new( - bounding_box => $expolygon->bounding_box, - angle => 0, - ); - my $surface = Slic3r::Surface->new( - surface_type => S_TYPE_BOTTOM, - expolygon => $expolygon, - ); - my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.55, density => 1); + $test->($expolygon); - # check whether any part was left uncovered - my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths; - my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ]); - is scalar(@$uncovered), 0, 'solid surface is fully filled'; + for (1..20) { + $expolygon->scale(1.05); + $test->($expolygon); + } } { From 80676f358acb062c64e6460dedf3418447e38a1a Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 13:36:22 +0200 Subject: [PATCH 31/52] Keep edge lines in rectilinear infill even when the sides are not perfectly straight --- lib/Slic3r/ExPolygon.pm | 6 ++++++ lib/Slic3r/Fill/Rectilinear.pm | 5 ++++- t/fill.t | 22 ++++++++++++++++------ 3 files changed, 26 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index bd5ed4e3fb..3db8a7241a 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -84,6 +84,12 @@ sub wkt { join ',', map "($_)", map { join ',', map "$_->[0] $_->[1]", @$_ } @$self; } +sub dump_perl { + my $self = shift; + return sprintf "[%s]", + join ',', map "[$_]", map { join ',', map "[$_->[0],$_->[1]]", @$_ } @$self; +} + sub offset { my $self = shift; return Slic3r::Geometry::Clipper::offset($self, @_); diff --git a/lib/Slic3r/Fill/Rectilinear.pm b/lib/Slic3r/Fill/Rectilinear.pm index c58b3871a0..50567dc399 100644 --- a/lib/Slic3r/Fill/Rectilinear.pm +++ b/lib/Slic3r/Fill/Rectilinear.pm @@ -55,8 +55,11 @@ sub fill_surface { # clip paths against a slightly offsetted expolygon, so that the first and last paths # are kept even if the expolygon has vertical sides + # the minimum offset for preventing edge lines from being clipped is scaled_epsilon; + # however we use a larger offset to support expolygons with slightly skewed sides and + # not perfectly straight my @paths = @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection( - [ $expolygon->offset_ex(scaled_epsilon) ], + [ $expolygon->offset_ex($line_spacing*0.05) ], [ @vertical_lines ], ) }; diff --git a/t/fill.t b/t/fill.t index 9ffce0ba18..ca064fac3f 100644 --- a/t/fill.t +++ b/t/fill.t @@ -2,7 +2,7 @@ use Test::More; use strict; use warnings; -plan tests => 31; +plan tests => 32; BEGIN { use FindBin; @@ -51,8 +51,8 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } { my $test = sub { - my ($expolygon) = @_; - $expolygon->align_to_origin; + my ($expolygon, $flow_spacing) = @_; + my $filler = Slic3r::Fill::Rectilinear->new( bounding_box => $expolygon->bounding_box, angle => 0, @@ -61,12 +61,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } surface_type => S_TYPE_BOTTOM, expolygon => $expolygon, ); - my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => 0.55, density => 1); + my ($params, @paths) = $filler->fill_surface($surface, flow_spacing => $flow_spacing, density => 1); # check whether any part was left uncovered my @grown_paths = map Slic3r::Polyline->new(@$_)->grow(scale $params->{flow_spacing}/2), @paths; my $uncovered = diff_ex([ @$expolygon ], [ @grown_paths ], 1); + + # ignore very small dots + @$uncovered = grep $_->area > (scale $flow_spacing)**2, @$uncovered; + is scalar(@$uncovered), 0, 'solid surface is fully filled'; + if (0 && @$uncovered) { require "Slic3r/SVG.pm"; Slic3r::SVG::output( @@ -84,12 +89,17 @@ sub scale_points (@) { map [scale $_->[X], scale $_->[Y]], @_ } [3116896, 20327272.01297], [3116896, 9598327.01296997], ]); - $test->($expolygon); + $test->($expolygon, 0.55); for (1..20) { $expolygon->scale(1.05); - $test->($expolygon); + $test->($expolygon, 0.55); } + + $expolygon = Slic3r::ExPolygon->new( + [[59515297,5422499],[59531249,5578697],[59695801,6123186],[59965713,6630228],[60328214,7070685],[60773285,7434379],[61274561,7702115],[61819378,7866770],[62390306,7924789],[62958700,7866744],[63503012,7702244],[64007365,7434357],[64449960,7070398],[64809327,6634999],[65082143,6123325],[65245005,5584454],[65266967,5422499],[66267307,5422499],[66269190,8310081],[66275379,17810072],[66277259,20697500],[65267237,20697500],[65245004,20533538],[65082082,19994444],[64811462,19488579],[64450624,19048208],[64012101,18686514],[63503122,18415781],[62959151,18251378],[62453416,18198442],[62390147,18197355],[62200087,18200576],[61813519,18252990],[61274433,18415918],[60768598,18686517],[60327567,19047892],[59963609,19493297],[59695865,19994587],[59531222,20539379],[59515153,20697500],[58502480,20697500],[58502480,5422499]] + ); + $test->($expolygon, 0.524341649025257); } { From 9adac636b8c8a5ad2149ece3e18fd762b9753a60 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 13:49:53 +0200 Subject: [PATCH 32/52] Avoid G92 E0 with sailfish. #1034 --- lib/Slic3r/GCode.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index e8b7e6dd02..b062940562 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -532,7 +532,7 @@ sub unretract { sub reset_e { my $self = shift; - return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware)$/; + return "" if $self->config->gcode_flavor =~ /^(?:mach3|makerware|sailfish)$/; $self->extruder->e(0) if $self->extruder; return sprintf "G92 %s0%s\n", $self->config->extrusion_axis, ($self->config->gcode_comments ? ' ; reset extrusion distance' : '') From 8fe38d9b716f6f0781ba6a430e249c4df0504100 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 14:16:33 +0200 Subject: [PATCH 33/52] Bugfix: fatal error when failed loops were included in slices. #1358 --- lib/Slic3r/TriangleMesh.pm | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index d6d5dc919d..8c1738d43c 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -267,10 +267,10 @@ sub make_loops { } # TODO: we should try to combine failed loops - for (grep @$_ >= 3, @failed_loops) { - push @polygons, Slic3r::Polygon->new(@$_); + for my $loop (grep @$_ >= 3, @failed_loops) { + push @polygons, Slic3r::Polygon->new(map $_->[I_A], @$loop); Slic3r::debugf " Discovered failed %s polygon of %d points\n", - ($polygons[-1]->is_counter_clockwise ? 'ccw' : 'cw'), scalar(@$_) + ($polygons[-1]->is_counter_clockwise ? 'ccw' : 'cw'), scalar(@$loop) if $Slic3r::debug; } From dc766f9f73f33615b1adc5c3f80071dd1839d954 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 14:56:37 +0200 Subject: [PATCH 34/52] Bugfix: the tangent edge removal algorithm was refactored the wrong way and wasn't used anymore --- lib/Slic3r/TriangleMesh.pm | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index 8c1738d43c..bebce77169 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -191,7 +191,7 @@ sub make_loops { next unless defined $lines[$j] && defined $lines[$j][I_FACET_EDGE]; # are these facets adjacent? (sharing a common edge on this layer) - if ($lines[$i][I_A_ID] == $lines[$j][I_B_ID] && $lines[$i][I_B_ID] == $lines[$j][I_A_ID]) { + if ($lines[$i][I_A_ID] == $lines[$j][I_A_ID] && $lines[$i][I_B_ID] == $lines[$j][I_B_ID]) { # if they are both oriented upwards or downwards (like a 'V') # then we can remove both edges from this layer since it won't @@ -205,7 +205,7 @@ sub make_loops { # if one of them is oriented upwards and the other is oriented # downwards, let's only keep one of them (it doesn't matter which # one since all 'top' lines were reversed at slicing) - if ($lines[$i][I_FACET_EDGE] == FE_TOP && $lines[$j][I_FACET_EDGE] == FE_BOTTOM) { + if ($lines[$i][I_FACET_EDGE] != $lines[$j][I_FACET_EDGE]) { $lines[$j] = undef; last; } From 1210b8989350b96ffbb0ddda39c6132e3eb3f40f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 16:43:48 +0200 Subject: [PATCH 35/52] Remove thumbnail simplification because it caused loss of very thin parts. #1327 --- lib/Slic3r/GUI/Plater.pm | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 9f3f956489..83fe4d7d4a 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1172,11 +1172,8 @@ sub make_thumbnail { ? $mesh->horizontal_projection : [ Slic3r::ExPolygon->new($self->convex_hull) ], ); - - # only simplify expolygons larger than the threshold - @{$thumbnail->expolygons} = grep @$_, - map { ($_->area >= 1) ? $_->simplify(0.5) : $_ } - @{$thumbnail->expolygons}; + # Note: the call to simplify() was removed here because it used Clipper + # simplification which needs integerization. $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; From b5907dc73463ae75d2975d236a593c949c052869 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 17:28:30 +0200 Subject: [PATCH 36/52] Bugfix: z_offset was not applied in spiral_vase. Includes regression test #1343 --- lib/Slic3r/GCode/Layer.pm | 2 +- lib/Slic3r/GCode/SpiralVase.pm | 4 +++- t/shells.t | 10 +++++++++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index df3e85b945..af296edf08 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -18,7 +18,7 @@ sub _build_spiralvase { my $self = shift; return $Slic3r::Config->spiral_vase - ? Slic3r::GCode::SpiralVase->new + ? Slic3r::GCode::SpiralVase->new(config => $self->gcodegen->config) : undef; } diff --git a/lib/Slic3r/GCode/SpiralVase.pm b/lib/Slic3r/GCode/SpiralVase.pm index 1c94b251a9..1094e728e2 100644 --- a/lib/Slic3r/GCode/SpiralVase.pm +++ b/lib/Slic3r/GCode/SpiralVase.pm @@ -1,6 +1,8 @@ package Slic3r::GCode::SpiralVase; use Moo; +has 'config' => (is => 'ro', required => 1); + use Slic3r::Geometry qw(unscale); sub process_layer { @@ -16,7 +18,7 @@ sub process_layer { my $new_gcode = ""; my $layer_height = $layer->height; - my $z = $layer->print_z - $layer_height; + my $z = $layer->print_z + $self->config->z_offset - $layer_height; my $newlayer = 0; Slic3r::GCode::Reader->new(gcode => $gcode)->parse(sub { my ($reader, $cmd, $args, $info) = @_; diff --git a/t/shells.t b/t/shells.t index 8cfcc87f69..bd4b221c65 100644 --- a/t/shells.t +++ b/t/shells.t @@ -1,4 +1,4 @@ -use Test::More tests => 6; +use Test::More tests => 10; use strict; use warnings; @@ -104,6 +104,7 @@ use Slic3r::Test; $config->set('spiral_vase', 1); $config->set('bottom_solid_layers', 0); $config->set('skirts', 0); + $config->set('first_layer_height', '100%'); # TODO: this needs to be tested with a model with sloping edges, where starting # points of each layer are not aligned - in that case we would test that no @@ -114,18 +115,25 @@ use Slic3r::Test; my $print = Slic3r::Test::init_print($model_name, config => $config); my $travel_moves_after_first_extrusion = 0; my $started_extruding = 0; + my @z_steps = (); Slic3r::GCode::Reader->new(gcode => Slic3r::Test::gcode($print))->parse(sub { my ($self, $cmd, $args, $info) = @_; $started_extruding = 1 if $info->{extruding}; + push @z_steps, ($args->{Z} - $self->Z) + if $started_extruding && exists $args->{Z}; $travel_moves_after_first_extrusion++ if $info->{travel} && $started_extruding && !exists $args->{Z}; }); is $travel_moves_after_first_extrusion, 0, "no gaps in spiral vase ($description)"; + ok !(grep { $_ > $config->layer_height } @z_steps), "no gaps in Z ($description)"; }; $test->('20mm_cube', 'solid model'); $test->('40x10', 'hollow model'); + + $config->set('z_offset', -10); + $test->('20mm_cube', 'solid model with negative z-offset'); } __END__ From 10a8f479f92ecf3c232b22642b35692e96b661eb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 19:43:57 +0200 Subject: [PATCH 37/52] Bugfix: lift was not working correctly with multiple extruders and multiple skirt layers. Also, we now ignore all lift settings except the one of the first extruder. Includes regression tests. #1332 #1338 --- lib/Slic3r/Config.pm | 2 +- lib/Slic3r/GCode.pm | 17 ++++++++++++++--- lib/Slic3r/Print.pm | 3 +++ t/retraction.t | 9 ++++++--- 4 files changed, 24 insertions(+), 7 deletions(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index a6c160acb4..03b568339e 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -814,7 +814,7 @@ END }, 'retract_lift' => { label => 'Lift Z', - tooltip => 'If you set this to a positive value, Z is quickly raised every time a retraction is triggered.', + tooltip => 'If you set this to a positive value, Z is quickly raised every time a retraction is triggered. When using multiple extruders, only the setting for the first extruder will be considered.', sidetext => 'mm', cli => 'retract-lift=f@', type => 'f', diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index b062940562..1df6e8a794 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -3,7 +3,7 @@ use Moo; use List::Util qw(min max first); use Slic3r::ExtrusionPath ':roles'; -use Slic3r::Geometry qw(scale unscale scaled_epsilon points_coincide PI X Y B); +use Slic3r::Geometry qw(epsilon scale unscale scaled_epsilon points_coincide PI X Y B); use Slic3r::Geometry::Clipper qw(union_ex); use Slic3r::Surface ':types'; @@ -128,12 +128,23 @@ sub move_z { my $gcode = ""; my $current_z = $self->z; - if (!defined $current_z || $current_z != ($z + $self->lifted)) { + if (!defined $self->z || $z > $self->z) { + # if we're going over the current Z we won't be lifted anymore + $self->lifted(0); + + # this retraction may alter $self->z $gcode .= $self->retract(move_z => $z) if $self->extruder->retract_layer_change; + $self->speed('travel'); $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) - unless ($current_z // -1) != ($self->z // -1); + unless !defined $current_z || $self->z != $current_z; $gcode .= $self->move_z_callback->() if defined $self->move_z_callback; + } elsif ($z < $self->z && $z > ($self->z - $self->lifted + epsilon)) { + # we're moving to a layer height which is greater than the nominal current one + # (nominal = actual - lifted) and less than the actual one. we're basically + # advancing to next layer, whose nominal Z is still lower than the previous + # layer Z with lift. + $self->lifted($self->z - $z); } return $gcode; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 531197582b..8c62d1a488 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -73,6 +73,9 @@ sub _trigger_config { $self->config->set('support_material_enforce_layers', 0); $self->config->set('retract_layer_change', [0]); # TODO: only apply this to the spiral layers } + + # force all retraction lift values to be the same + $self->config->set('retract_lift', [ map $self->config->retract_lift->[0], @{$self->config->retract_lift} ]); } sub _build_has_support_material { diff --git a/t/retraction.t b/t/retraction.t index cb13667769..08276ad6ea 100644 --- a/t/retraction.t +++ b/t/retraction.t @@ -48,8 +48,9 @@ my $test = sub { } if ($info->{dist_Z} < 0) { fail 'going down only after lifting' if !$lifted; - fail 'going down by the same amount of the lift' - if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift); + fail 'going down by the same amount of the lift or by the amount needed to get to next layer' + if !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift) + && !_eq($info->{dist_Z}, -$print->extruders->[$tool]->retract_lift + $conf->layer_height); $lifted = 0; } fail 'move Z at travel speed' if ($args->{F} // $self->F) != $conf->travel_speed * 60; @@ -123,6 +124,8 @@ $retract_tests->(' (G0 and duplicate)'); $config->set('duplicate', 1); $config->set('g0', 0); $config->set('infill_extruder', 2); -$retract_tests->(' (dual extruder)'); +$config->set('skirts', 4); +$config->set('skirt_height', 3); +$retract_tests->(' (dual extruder with multiple skirt layers)'); __END__ From a2cc230bb5babaff3fd5b2c20183e3feea19b507 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 19:50:47 +0200 Subject: [PATCH 38/52] Add note about a bug caused by the disabled optimization about splitting meshes before avoid_crossing_perimeters. #1315 --- lib/Slic3r/Print.pm | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 8c62d1a488..49b25ccdd2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -107,6 +107,9 @@ sub add_model { # this mesh into distinct objects so that we reduce the complexity # of the graphs # -- Disabling this one because there are too many legit objects having nested shells + # -- It also caused a bug where plater rotation was applied to each single object by the + # -- code below (thus around its own center), instead of being applied to the whole + # -- thing before the split. ###$model->split_meshes if $Slic3r::Config->avoid_crossing_perimeters && !$Slic3r::Config->complete_objects; foreach my $object (@{ $model->objects }) { From 913f401280130831cdd3d2315b864b6ab60043c8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 20:49:54 +0200 Subject: [PATCH 39/52] Merge new-support2 --- README.markdown | 2 + lib/Slic3r/Config.pm | 13 +- lib/Slic3r/GCode/CoolingBuffer.pm | 2 - lib/Slic3r/GCode/Layer.pm | 18 +- lib/Slic3r/GUI/Tab.pm | 2 +- lib/Slic3r/Geometry/Clipper.pm | 13 +- lib/Slic3r/Layer.pm | 40 +- lib/Slic3r/Polyline.pm | 32 ++ lib/Slic3r/Print.pm | 55 ++- lib/Slic3r/Print/Object.pm | 659 +++++++++++++++++++----------- lib/Slic3r/Test/SectionCut.pm | 4 +- slic3r.pl | 2 + t/support.t | 45 +- 13 files changed, 578 insertions(+), 309 deletions(-) diff --git a/README.markdown b/README.markdown index 3d1ef28487..46fbd0e635 100644 --- a/README.markdown +++ b/README.markdown @@ -322,6 +322,8 @@ The author of the Silk icon set is Mark James. --infill-extruder Extruder to use for infill (1+, default: 1) --support-material-extruder Extruder to use for support material (1+, default: 1) + --support-material-interface-extruder + Extruder to use for support material interface (1+, default: 1) If you want to change a preset file, just do diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index 03b568339e..b45ef47ee2 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -204,11 +204,18 @@ our $Options = { }, 'support_material_extruder' => { label => 'Support material extruder', - tooltip => 'The extruder to use when printing support material. This affects brim too.', + tooltip => 'The extruder to use when printing support material. This affects brim and raft too.', cli => 'support-material-extruder=i', type => 'i', default => 1, }, + 'support_material_interface_extruder' => { + label => 'Support material interface extruder', + tooltip => 'The extruder to use when printing support material interface. This affects raft too.', + cli => 'support-material-interface-extruder=i', + type => 'i', + default => 1, + }, # filament options 'first_layer_bed_temperature' => { @@ -652,7 +659,7 @@ our $Options = { type => 'select', values => [qw(rectilinear rectilinear-grid honeycomb)], labels => ['rectilinear', 'rectilinear grid', 'honeycomb'], - default => 'rectilinear', + default => 'honeycomb', }, 'support_material_spacing' => { label => 'Pattern spacing', @@ -676,7 +683,7 @@ our $Options = { sidetext => 'layers', cli => 'support-material-interface-layers=i', type => 'i', - default => 0, + default => 3, }, 'support_material_interface_spacing' => { label => 'Interface pattern spacing', diff --git a/lib/Slic3r/GCode/CoolingBuffer.pm b/lib/Slic3r/GCode/CoolingBuffer.pm index a4f44fe260..45283bef65 100644 --- a/lib/Slic3r/GCode/CoolingBuffer.pm +++ b/lib/Slic3r/GCode/CoolingBuffer.pm @@ -18,8 +18,6 @@ sub append { my $self = shift; my ($gcode, $obj_id, $layer_id, $print_z) = @_; - # TODO: differentiate $obj_id between normal layers and support layers - my $return = ""; if (exists $self->last_z->{$obj_id} && $self->last_z->{$obj_id} != $print_z) { $return = $self->flush; diff --git a/lib/Slic3r/GCode/Layer.pm b/lib/Slic3r/GCode/Layer.pm index af296edf08..307ab0e32e 100644 --- a/lib/Slic3r/GCode/Layer.pm +++ b/lib/Slic3r/GCode/Layer.pm @@ -97,17 +97,15 @@ sub process_layer { # extrude support material before other things because it might use a lower Z # and also because we avoid travelling on other things when printing it - if ($self->print->has_support_material) { - $gcode .= $self->gcodegen->move_z($layer->support_material_contact_z) - if ($layer->support_contact_fills && @{ $layer->support_contact_fills->paths }); - $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); - if ($layer->support_contact_fills) { - $gcode .= $self->gcodegen->extrude_path($_, 'support material contact area') - for $layer->support_contact_fills->chained_path($self->gcodegen->last_pos); - } - + if ($self->print->has_support_material && $layer->isa('Slic3r::Layer::Support')) { $gcode .= $self->gcodegen->move_z($layer->print_z); + if ($layer->support_interface_fills) { + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_interface_extruder-1]); + $gcode .= $self->gcodegen->extrude_path($_, 'support material interface') + for $layer->support_interface_fills->chained_path($self->gcodegen->last_pos); + } if ($layer->support_fills) { + $gcode .= $self->gcodegen->set_extruder($self->extruders->[$Slic3r::Config->support_material_extruder-1]); $gcode .= $self->gcodegen->extrude_path($_, 'support material') for $layer->support_fills->chained_path($self->gcodegen->last_pos); } @@ -125,7 +123,7 @@ sub process_layer { } foreach my $region_id (@region_ids) { - my $layerm = $layer->regions->[$region_id]; + my $layerm = $layer->regions->[$region_id] or next; my $region = $self->print->regions->[$region_id]; my @islands = (); diff --git a/lib/Slic3r/GUI/Tab.pm b/lib/Slic3r/GUI/Tab.pm index 27ff3c56e0..70fd3962a8 100644 --- a/lib/Slic3r/GUI/Tab.pm +++ b/lib/Slic3r/GUI/Tab.pm @@ -526,7 +526,7 @@ sub build { $self->add_options_page('Multiple Extruders', 'funnel.png', optgroups => [ { title => 'Extruders', - options => [qw(perimeter_extruder infill_extruder support_material_extruder)], + options => [qw(perimeter_extruder infill_extruder support_material_extruder support_material_interface_extruder)], }, ]); diff --git a/lib/Slic3r/Geometry/Clipper.pm b/lib/Slic3r/Geometry/Clipper.pm index 35d13c874b..fc8d35b8a0 100644 --- a/lib/Slic3r/Geometry/Clipper.pm +++ b/lib/Slic3r/Geometry/Clipper.pm @@ -7,7 +7,7 @@ our @ISA = qw(Exporter); our @EXPORT_OK = qw(safety_offset safety_offset_ex offset offset_ex collapse_ex diff_ex diff union_ex intersection_ex xor_ex PFT_EVENODD JT_MITER JT_ROUND JT_SQUARE is_counter_clockwise union_pt offset2 offset2_ex traverse_pt - intersection); + intersection union); use Math::Clipper 1.22 qw(:cliptypes :polyfilltypes :jointypes is_counter_clockwise area); use Slic3r::Geometry qw(scale); @@ -88,6 +88,17 @@ sub diff { ]; } +sub union { + my ($polygons, $jointype, $safety_offset) = @_; + $jointype = PFT_NONZERO unless defined $jointype; + $clipper->clear; + $clipper->add_subject_polygons($safety_offset ? safety_offset($polygons) : $polygons); + return [ + map Slic3r::Polygon->new(@$_), + @{ $clipper->execute(CT_UNION, $jointype, $jointype) }, + ]; +} + sub union_ex { my ($polygons, $jointype, $safety_offset) = @_; $jointype = PFT_NONZERO unless defined $jointype; diff --git a/lib/Slic3r/Layer.pm b/lib/Slic3r/Layer.pm index ece5062532..d1bc2818b5 100644 --- a/lib/Slic3r/Layer.pm +++ b/lib/Slic3r/Layer.pm @@ -18,42 +18,11 @@ has 'height' => (is => 'ro', required => 1); # layer height in unscal # also known as 'islands' (all regions and surface types are merged here) has 'slices' => (is => 'rw'); -# ordered collection of extrusion paths to fill surfaces for support material -has 'support_islands' => (is => 'rw'); -has 'support_fills' => (is => 'rw'); -has 'support_contact_fills' => (is => 'rw'); - sub _trigger_id { my $self = shift; $_->_trigger_layer for @{$self->regions || []}; } -# layer height of contact paths in unscaled coordinates -sub support_material_contact_height { - my $self = shift; - - return $self->height if $self->id == 0; - - # TODO: check what upper region applies instead of considering the first one - my $upper_layer = $self->object->layers->[ $self->id + 1 ] // $self; - my $h = ($self->height + $upper_layer->height) - $upper_layer->regions->[0]->extruders->{infill}->bridge_flow->width; - - # If layer height is less than half the bridge width then we'll get a negative height for contact area. - # The optimal solution would be to skip some layers during support material generation, but for now - # we'll apply a (dirty) workaround that should still work. - if ($h <= 0) { - $h = $self->height; - } - - return $h; -} - -# Z used for printing support material contact in scaled coordinates -sub support_material_contact_z { - my $self = shift; - return ($self->print_z - ($self->height - $self->support_material_contact_height)) / &Slic3r::SCALING_FACTOR; -} - sub upper_layer_slices { my $self = shift; @@ -94,4 +63,13 @@ sub support_islands_enclose_line { return (first { $_->encloses_line($line) } @{$self->support_islands}) ? 1 : 0; } +package Slic3r::Layer::Support; +use Moo; +extends 'Slic3r::Layer'; + +# ordered collection of extrusion paths to fill surfaces for support material +has 'support_islands' => (is => 'rw'); +has 'support_fills' => (is => 'rw'); +has 'support_interface_fills' => (is => 'rw'); + 1; diff --git a/lib/Slic3r/Polyline.pm b/lib/Slic3r/Polyline.pm index fd4bd571f8..0196cf677b 100644 --- a/lib/Slic3r/Polyline.pm +++ b/lib/Slic3r/Polyline.pm @@ -211,6 +211,38 @@ sub clip_start { return (ref $self)->new($points); } +# this method returns a collection of points picked on the polygon contour +# so that they are evenly spaced according to the input distance +# (find a better name!) +sub regular_points { + my $self = shift; + my ($distance) = @_; + + my @points = ($self->[0]); + my $len = 0; + + for (my $i = 1; $i <= $#$self; $i++) { + my $point = $self->[$i]; + my $segment_length = $point->distance_to($self->[$i-1]); + $len += $segment_length; + next if $len < $distance; + + if ($len == $distance) { + push @points, $point; + $len = 0; + next; + } + + my $take = $segment_length - ($len - $distance); # how much we take of this segment + my $new_point = Slic3r::Geometry::point_along_segment($self->[$i-1], $point, $take); + push @points, Slic3r::Point->new($new_point); + $i--; + $len = -$take; + } + + return @points; +} + package Slic3r::Polyline::Collection; use Moo; diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index 49b25ccdd2..d12a6e87f4 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -3,7 +3,7 @@ use Moo; use File::Basename qw(basename fileparse); use File::Spec; -use List::Util qw(max first); +use List::Util qw(min max first); use Math::ConvexHull::MonotoneChain qw(convex_hull); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(X Y Z X1 Y1 X2 Y2 MIN MAX PI scale unscale move_points @@ -263,6 +263,7 @@ sub init_extruders { } # calculate support material flow + # Note: we should calculate a different flow for support material interface if ($self->has_support_material) { my $extruder = $self->extruders->[$self->config->support_material_extruder-1]; $self->support_material_flow($extruder->make_flow( @@ -581,15 +582,18 @@ sub make_skirt { # collect points from all layers contained in skirt height my @points = (); foreach my $obj_idx (0 .. $#{$self->objects}) { - my $skirt_height = $Slic3r::Config->skirt_height; - $skirt_height = $self->objects->[$obj_idx]->layer_count if $skirt_height > $self->objects->[$obj_idx]->layer_count; - my @layers = map $self->objects->[$obj_idx]->layers->[$_], 0..($skirt_height-1); + my $object = $self->objects->[$obj_idx]; + my @layers = map $object->layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->layers}); my @layer_points = ( (map @$_, map @$_, map @{$_->slices}, @layers), (map @$_, map @{$_->thin_walls}, map @{$_->regions}, @layers), - (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @layers), ); - push @points, map move_points($_, @layer_points), @{$self->objects->[$obj_idx]->copies}; + if (@{ $object->support_layers }) { + my @support_layers = map $object->support_layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->support_layers}); + push @layer_points, + (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @support_layers); + } + push @points, map move_points($_, @layer_points), @{$object->copies}; } return if @points < 3; # at least three points required for a convex hull @@ -644,13 +648,19 @@ sub make_brim { my $grow_distance = $flow->scaled_width / 2; my @islands = (); # array of polygons foreach my $obj_idx (0 .. $#{$self->objects}) { - my $layer0 = $self->objects->[$obj_idx]->layers->[0]; + my $object = $self->objects->[$obj_idx]; + my $layer0 = $object->layers->[0]; my @object_islands = ( (map $_->contour, @{$layer0->slices}), (map { $_->isa('Slic3r::Polygon') ? $_ : $_->grow($grow_distance) } map @{$_->thin_walls}, @{$layer0->regions}), - (map $_->unpack->polyline->grow($grow_distance), map @{$_->support_fills->paths}, grep $_->support_fills, $layer0), ); - foreach my $copy (@{$self->objects->[$obj_idx]->copies}) { + if (@{ $object->support_layers }) { + my $support_layer0 = $object->support_layers->[0]; + push @object_islands, + (map $_->unpack->polyline->grow($grow_distance), @{$support_layer0->support_fills->paths}) + if $support_layer0->support_fills; + } + foreach my $copy (@{$object->copies}) { push @islands, map $_->clone->translate(@$copy), @object_islands; } } @@ -812,7 +822,9 @@ sub write_gcode { gcodegen => $gcodegen, ); - for my $layer (@{$self->objects->[$obj_idx]->layers}) { + my $object = $self->objects->[$obj_idx]; + my @layers = sort { $a->print_z <=> $b->print_z } @{$object->layers}, @{$object->support_layers}; + for my $layer (@layers) { # if we are printing the bottom layer of an object, and we have already finished # another one, set first layer temperatures. this happens before the Z move # is triggered, so machine has more time to reach such temperatures @@ -837,11 +849,13 @@ sub write_gcode { my @obj_idx = chained_path([ map $_->copies->[0], @{$self->objects} ]); # sort layers by Z - my %layers = (); # print_z => [ layer, layer, layer ] by obj_idx + my %layers = (); # print_z => [ [layers], [layers], [layers] ] by obj_idx foreach my $obj_idx (0 .. $#{$self->objects}) { - foreach my $layer (@{$self->objects->[$obj_idx]->layers}) { + my $object = $self->objects->[$obj_idx]; + foreach my $layer (@{$object->layers}, @{$object->support_layers}) { $layers{ $layer->print_z } ||= []; - $layers{ $layer->print_z }[$obj_idx] = $layer; # turn this into [$layer] when merging support layers + $layers{ $layer->print_z }[$obj_idx] ||= []; + push @{$layers{ $layer->print_z }[$obj_idx]}, $layer; } } @@ -851,13 +865,14 @@ sub write_gcode { ); foreach my $print_z (sort { $a <=> $b } keys %layers) { foreach my $obj_idx (@obj_idx) { - next unless my $layer = $layers{$print_z}[$obj_idx]; - print $fh $buffer->append( - $layer_gcode->process_layer($layer, $layer->object->copies), - $layer->object."", - $layer->id, - $layer->print_z, - ); + foreach my $layer (@{ $layers{$print_z}[$obj_idx] // [] }) { + print $fh $buffer->append( + $layer_gcode->process_layer($layer, $layer->object->copies), + $layer->object . ref($layer), # differentiate $obj_id between normal layers and support layers + $layer->id, + $layer->print_z, + ); + } } } print $fh $buffer->flush; diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 8bd80d1518..d71ff140dc 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -1,11 +1,11 @@ package Slic3r::Print::Object; use Moo; -use List::Util qw(min sum first); +use List::Util qw(min max sum first); use Slic3r::ExtrusionPath ':roles'; use Slic3r::Geometry qw(Z PI scale unscale deg2rad rad2deg scaled_epsilon chained_path_points); -use Slic3r::Geometry::Clipper qw(diff_ex intersection_ex union_ex offset collapse_ex - offset2 diff intersection); +use Slic3r::Geometry::Clipper qw(diff diff_ex intersection intersection_ex union union_ex + offset offset_ex offset2); use Slic3r::Surface ':types'; has 'print' => (is => 'ro', weak_ref => 1, required => 1); @@ -14,6 +14,7 @@ has 'meshes' => (is => 'rw', default => sub { [] }); # by region_id has 'size' => (is => 'rw', required => 1); # XYZ in scaled coordinates has 'copies' => (is => 'rw', trigger => 1); # in scaled coordinates has 'layers' => (is => 'rw', default => sub { [] }); +has 'support_layers' => (is => 'rw', default => sub { [] }); has 'layer_height_ranges' => (is => 'rw', default => sub { [] }); # [ z_min, z_max, layer_height ] has 'fill_maker' => (is => 'lazy'); has '_slice_z_table' => (is => 'lazy'); @@ -808,264 +809,446 @@ sub generate_support_material { my $self = shift; return if $self->layer_count < 2; + my $flow = $self->print->support_material_flow; + + # how much we extend support around the actual contact area + #my $margin = $flow->scaled_width / 2; + my $margin = scale 3; + + # increment used to reach $margin in steps to avoid trespassing thin objects + my $margin_step = $margin/3; + + # if user specified a custom angle threshold, convert it to radians my $threshold_rad; if ($Slic3r::Config->support_material_threshold) { $threshold_rad = deg2rad($Slic3r::Config->support_material_threshold + 1); # +1 makes the threshold inclusive Slic3r::debugf "Threshold angle = %d°\n", rad2deg($threshold_rad); } - my $flow = $self->print->support_material_flow; - my $distance_from_object = 1.5 * $flow->scaled_width; - my $pattern_spacing = ($Slic3r::Config->support_material_spacing > $flow->spacing) - ? $Slic3r::Config->support_material_spacing - : $flow->spacing; - # determine support regions in each layer (for upper layers) - Slic3r::debugf "Detecting regions\n"; - my %layers = (); # this represents the areas of each layer having to support upper layers (excluding interfaces) - my %layers_interfaces = (); # this represents the areas of each layer to be filled with interface pattern, excluding the contact areas which are stored separately - my %layers_contact_areas = (); # this represents the areas of each layer having an overhang in the immediately upper layer + # shape of contact area + my $contact_loops = 1; + my $circle_distance = 3 * $flow->scaled_width; + my $circle; { - my @current_support_regions = (); # expolygons we've started to support (i.e. below the empty interface layers) - my @upper_layers_overhangs = (map [], 1..$Slic3r::Config->support_material_interface_layers); - for my $i (reverse 0 .. $#{$self->layers}) { - next unless $Slic3r::Config->support_material - || ($i <= $Slic3r::Config->raft_layers) # <= because we need to start from the first non-raft layer - || ($i <= $Slic3r::Config->support_material_enforce_layers + $Slic3r::Config->raft_layers); + # TODO: make sure teeth between circles are compatible with support material flow + my $r = 1.5 * $flow->scaled_width; + $circle = Slic3r::Polygon->new(map [ $r * cos $_, $r * sin $_ ], (5*PI/3, 4*PI/3, PI, 2*PI/3, PI/3, 0)); + } + + # determine contact areas + my %contact = (); # contact_z => [ polygons ] + my %overhang = (); # contact_z => [ expolygons ] - this stores the actual overhang supported by each contact layer + for my $layer_id (1 .. $#{$self->layers}) { + my $layer = $self->layers->[$layer_id]; + my $lower_layer = $self->layers->[$layer_id-1]; + + # detect overhangs and contact areas needed to support them + my (@overhang, @contact) = (); + foreach my $layerm (@{$layer->regions}) { + my $fw = $layerm->perimeter_flow->scaled_width; + my $diff; - my $layer = $self->layers->[$i]; - my $lower_layer = $i > 0 ? $self->layers->[$i-1] : undef; - - my @current_layer_offsetted_slices = map $_->offset_ex($distance_from_object), @{$layer->slices}; - - # $upper_layers_overhangs[-1] contains the overhangs of the upper layer, regardless of any interface layers - # $upper_layers_overhangs[0] contains the overhangs of the first upper layer above the interface layers - - # we only consider the overhangs of the upper layer to define contact areas of the current one - $layers_contact_areas{$i} = diff_ex( - [ map @$_, @{ $upper_layers_overhangs[-1] || [] } ], - [ map @$_, @current_layer_offsetted_slices ], - ); - $layers_contact_areas{$i} = [ - @{collapse_ex([ map @$_, @{$layers_contact_areas{$i}} ], $flow->scaled_width)}, - ]; - - # to define interface regions of this layer we consider the overhangs of all the upper layers - # minus the first one - $layers_interfaces{$i} = diff_ex( - [ map @$_, map @$_, @upper_layers_overhangs[0 .. $#upper_layers_overhangs-1] ], - [ - (map @$_, @current_layer_offsetted_slices), - (map @$_, @{ $layers_contact_areas{$i} }), - ], - ); - $layers_interfaces{$i} = [ - @{collapse_ex([ map @$_, @{$layers_interfaces{$i}} ], $flow->scaled_width)}, - ]; - - # generate support material in current layer (for upper layers) - @current_support_regions = @{diff_ex( - [ - (map @$_, @current_support_regions), - (map @$_, @{ $upper_layers_overhangs[-1] || [] }), # only considering -1 instead of the whole array contents is just an optimization - ], - [ map @$_, @{$layer->slices} ], - )}; - shift @upper_layers_overhangs; - - $layers{$i} = diff_ex( - [ map @$_, @current_support_regions ], - [ - (map @$_, @current_layer_offsetted_slices), - (map @$_, @{ $layers_interfaces{$i} }), - ], - ); - $layers{$i} = [ - @{collapse_ex([ map @$_, @{$layers{$i}} ], $flow->scaled_width)}, - ]; - - # get layer overhangs and put them into queue for adding support inside lower layers; - # we need an angle threshold for this - my @overhangs = (); - if ($lower_layer) { - # consider all overhangs regardless of their angle if we're told to enforce support on this layer - my $distance = $i <= ($Slic3r::Config->support_material_enforce_layers + $Slic3r::Config->raft_layers) - ? 0 - : $Slic3r::Config->support_material_threshold - ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) - : $self->layers->[1]->regions->[0]->overhang_width; + # If a threshold angle was specified, use a different logic for detecting overhangs. + if (defined $threshold_rad || $layer_id <= $Slic3r::Config->support_material_enforce_layers) { + my $d = defined $threshold_rad + ? scale $lower_layer->height * ((cos $threshold_rad) / (sin $threshold_rad)) + : 0; - @overhangs = map $_->offset_ex(+$distance), @{diff_ex( - [ map @$_, @{$layer->slices} ], + $diff = diff( + [ offset([ map $_->p, @{$layerm->slices} ], -$d) ], [ map @$_, @{$lower_layer->slices} ], - 1, + ); + + # only enforce spacing from the object ($fw/2) if the threshold angle + # is not too high: in that case, $d will be very small (as we need to catch + # very short overhangs), and such contact area would be eaten by the + # enforced spacing, resulting in high threshold angles to be almost ignored + $diff = diff( + [ offset($diff, $d - $fw/2) ], + [ map @$_, @{$lower_layer->slices} ], + ) if $d > $fw/2; + } else { + $diff = diff( + [ offset([ map $_->p, @{$layerm->slices} ], -$fw/2) ], + [ map @$_, @{$lower_layer->slices} ], + ); + # $diff now contains the ring or stripe comprised between the boundary of + # lower slices and the centerline of the last perimeter in this overhanging layer. + # Void $diff means that there's no upper perimeter whose centerline is + # outside the lower slice boundary, thus no overhang + } + + next if !@$diff; + push @overhang, @{union_ex($diff)}; # NOTE: this is not the full overhang as it misses the outermost half of the perimeter width! + + # Let's define the required contact area by using a max gap of half the upper + # extrusion width and extending the area according to the configured margin. + # We increment the area in steps because we don't want our support to overflow + # on the other side of the object (if it's very thin). + { + my @slices_margin = offset([ map @$_, @{$lower_layer->slices} ], $fw/2); + for ($fw/2, map {$margin_step} 1..($margin / $margin_step)) { + $diff = diff( + [ offset($diff, $_) ], + \@slices_margin, + ); + } + } + push @contact, @$diff; + } + next if !@contact; + + # now apply the contact areas to the layer were they need to be made + { + # get the average nozzle diameter used on this layer + my @nozzle_diameters = map $_->nozzle_diameter, + map { $_->perimeter_flow, $_->solid_infill_flow } + @{$layer->regions}; + my $nozzle_diameter = sum(@nozzle_diameters)/@nozzle_diameters; + + my $contact_z = $layer->print_z - $nozzle_diameter * 1.5; + ###$contact_z = $layer->print_z - $layer->height; + $contact{$contact_z} = [ @contact ]; + $overhang{$contact_z} = [ @overhang ]; + } + } + my @contact_z = sort keys %contact; + + # find object top surfaces + # we'll use them to clip our support and detect where does it stick + my %top = (); # print_z => [ expolygons ] + { + my $projection = []; + foreach my $layer (reverse @{$self->layers}) { + if (my @top = grep $_->surface_type == S_TYPE_TOP, map @{$_->slices}, @{$layer->regions}) { + # compute projection of the contact areas above this top layer + # first add all the 'new' contact areas to the current projection + # ('new' means all the areas that are lower than the last top layer + # we considered) + my $min_top = min(keys %top) // max(keys %contact); + push @$projection, map @{$contact{$_}}, grep { $_ > $layer->print_z && $_ < $min_top } keys %contact; + + # now find whether any projection falls onto this top surface + my $touching = intersection($projection, [ map $_->p, @top ]); + if (@$touching) { + $top{ $layer->print_z } = $touching; + } + + # remove the areas that touched from the projection that will continue on + # next, lower, top surfaces + $projection = diff($projection, $touching); + } + } + } + my @top_z = sort keys %top; + + # we now know the upper and lower boundaries for our support material object + # (@contact_z and @top_z), so we can generate intermediate layers + my @support_layers = _compute_support_layers(\@contact_z, \@top_z, $Slic3r::Config, $flow); + + # if we wanted to apply some special logic to the first support layers lying on + # object's top surfaces this is the place to detect them + + # Let's now determine shells (interface layers) and normal support below them. + # Let's now fill each support layer by generating shells (interface layers) and + # clipping support area to the actual object boundaries. + my %interface = (); # layer_id => [ polygons ] + my %support = (); # layer_id => [ polygons ] + my $interface_layers = $Slic3r::Config->support_material_interface_layers; + for my $layer_id (0 .. $#support_layers) { + my $z = $support_layers[$layer_id]; + my $this = $contact{$z} // next; + # count contact layer as interface layer + for (my $i = $layer_id; $i >= 0 && $i > $layer_id-$interface_layers; $i--) { + $z = $support_layers[$i]; + # Compute interface area on this layer as diff of upper contact area + # (or upper interface area) and layer slices. + # This diff is responsible of the contact between support material and + # the top surfaces of the object. We should probably offset the top + # surfaces before performing the diff, but this needs investigation. + $this = $interface{$i} = diff( + [ + @$this, + @{ $interface{$i} || [] }, + ], + [ + @{ $top{$z} || [] }, + ], + ); + } + + # determine what layers does our support belong to + for (my $i = $layer_id-$interface_layers; $i >= 0; $i--) { + $z = $support_layers[$i]; + # Compute support area on this layer as diff of upper support area + # and layer slices. + $this = $support{$i} = diff( + [ + @$this, + @{ $support{$i} || [] }, + ], + [ + @{ $top{$z} || [] }, + @{ $interface{$i} || [] }, + ], + ); + } + } + + push @{$self->support_layers}, map Slic3r::Layer::Support->new( + object => $self, + id => $_, + height => ($_ == 0) ? $support_layers[$_] : ($support_layers[$_] - $support_layers[$_-1]), + print_z => $support_layers[$_], + slice_z => -1, + slices => [], + ), 0 .. $#support_layers; + + Slic3r::debugf "Generating patterns\n"; + + # prepare fillers + my $pattern = $Slic3r::Config->support_material_pattern; + my @angles = ($Slic3r::Config->support_material_angle); + if ($pattern eq 'rectilinear-grid') { + $pattern = 'rectilinear'; + push @angles, $angles[0] + 90; + } + + my %fillers = ( + interface => $self->fill_maker->filler('rectilinear'), + support => $self->fill_maker->filler($pattern), + ); + + my $interface_angle = $Slic3r::Config->support_material_angle + 90; + my $interface_spacing = $Slic3r::Config->support_material_interface_spacing + $flow->spacing; + my $interface_density = $interface_spacing == 0 ? 1 : $flow->spacing / $interface_spacing; + my $support_spacing = $Slic3r::Config->support_material_spacing + $flow->spacing; + my $support_density = $support_spacing == 0 ? 1 : $flow->spacing / $support_spacing; + + my $process_layer = sub { + my ($layer_id) = @_; + my $result = { contact => [], interface => [], support => [] }; + + $contact{$layer_id} ||= []; + $interface{$layer_id} ||= []; + $support{$layer_id} ||= []; + + # contact + if ((my $contact = $contact{$support_layers[$layer_id]}) && $contact_loops > 0) { + my $overhang = $overhang{$support_layers[$layer_id]}; + $contact = [ grep $_->is_counter_clockwise, @$contact ]; + + # generate the outermost loop + my @loops0; + { + # find centerline of the external loop of the contours + my @external_loops = offset($contact, -$flow->scaled_width/2); + + # apply a pattern to the loop + my @positions = map Slic3r::Polygon->new(@$_)->split_at_first_point->regular_points($circle_distance), @external_loops; + @loops0 = @{diff( + [ @external_loops ], + [ map $circle->clone->translate(@$_), @positions ], )}; } - push @upper_layers_overhangs, [@overhangs]; - if ($Slic3r::debug) { - printf "Layer %d (z = %.2f) has %d generic support areas, %d normal interface areas, %d contact areas\n", - $i, $layer->print_z, scalar(@{$layers{$i}}), scalar(@{$layers_interfaces{$i}}), scalar(@{$layers_contact_areas{$i}}); + # make more loops + my @loops = @loops0; + for my $i (2..$contact_loops) { + my $d = ($i-1) * $flow->scaled_spacing; + push @loops, offset2(\@loops0, -$d -0.5*$flow->scaled_spacing, +0.5*$flow->scaled_spacing); } - } - } - return if !map @$_, values %layers; - - # generate paths for the pattern that we're going to use - Slic3r::debugf "Generating patterns\n"; - my $support_patterns = []; - my $support_interface_patterns = []; - { - # 0.5 ensures the paths don't get clipped externally when applying them to layers - my @areas = map $_->offset_ex(- 0.5 * $flow->scaled_width), - @{union_ex([ map $_->contour, map @$_, values %layers, values %layers_interfaces, values %layers_contact_areas ])}; - - my $pattern = $Slic3r::Config->support_material_pattern; - my @angles = ($Slic3r::Config->support_material_angle); - if ($pattern eq 'rectilinear-grid') { - $pattern = 'rectilinear'; - push @angles, $angles[0] + 90; - } - - my $filler = $self->fill_maker->filler($pattern); - my $make_pattern = sub { - my ($expolygon, $density) = @_; - my @paths = $filler->fill_surface( - Slic3r::Surface->new(expolygon => $expolygon), - density => $density, - flow_spacing => $flow->spacing, + # clip such loops to the side oriented towards the object + @loops = map Slic3r::Polyline->new(@$_), + @{ Boost::Geometry::Utils::multi_polygon_multi_linestring_intersection( + [ offset_ex([ map @$_, @$overhang ], +scale 3) ], + [ map Slic3r::Polygon->new(@$_)->split_at_first_point, @loops ], + ) }; + + # subtract loops from the contact area to detect the remaining part + $interface{$layer_id} = intersection( + $interface{$layer_id}, + [ offset2(\@loops0, -($contact_loops) * $flow->scaled_spacing, +0.5*$flow->scaled_spacing) ], ); - my $params = shift @paths; - return map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), + # transform loops into ExtrusionPath objects + @loops = map Slic3r::ExtrusionPath->pack( + polyline => $_, role => EXTR_ROLE_SUPPORTMATERIAL, - height => undef, - flow_spacing => $params->{flow_spacing}, - ), @paths; - }; - foreach my $angle (@angles) { - $filler->angle($angle); - { - my $density = $flow->spacing / $pattern_spacing; - push @$support_patterns, [ map $make_pattern->($_, $density), @areas ]; - } + flow_spacing => $flow->spacing, + ), @loops; - if ($Slic3r::Config->support_material_interface_layers > 0) { - # if pattern is not cross-hatched, rotate the interface pattern by 90° degrees - $filler->angle($angle + 90) if @angles == 1; - - my $spacing = $Slic3r::Config->support_material_interface_spacing; - my $density = $spacing == 0 ? 1 : $flow->spacing / $spacing; - push @$support_interface_patterns, [ map $make_pattern->($_, $density), @areas ]; - } + $result->{contact} = [ @loops ]; } - - if (0) { - require "Slic3r/SVG.pm"; - Slic3r::SVG::output("support_$_.svg", - polylines => [ map $_->polyline, map @$_, $support_patterns->[$_] ], - red_polylines => [ map $_->polyline, map @$_, $support_interface_patterns->[$_] ], - polygons => [ map @$_, @areas ], - ) for 0 .. $#$support_patterns; - } - } - - # apply the pattern to layers - Slic3r::debugf "Applying patterns\n"; - { - my $clip_pattern = sub { - my ($layer_id, $expolygons, $height, $is_interface) = @_; - my @paths = (); - foreach my $expolygon (@$expolygons) { - push @paths, - map $_->pack, - map { - $_->height($height); - - # useless line because this coderef isn't called for layer 0 anymore; - # let's keep it here just in case we want to make the base flange optional - # in the future - $_->flow_spacing($self->print->first_layer_support_material_flow->spacing) - if $layer_id == 0; - - $_; - } - map $_->clip_with_expolygon($expolygon), - ###map $_->clip_with_polygon($expolygon->bounding_box->polygon), # currently disabled as a workaround for Boost failing at being idempotent - ($is_interface && @$support_interface_patterns) - ? @{$support_interface_patterns->[ $layer_id % @$support_interface_patterns ]} - : @{$support_patterns->[ $layer_id % @$support_patterns ]}; - }; - return @paths; - }; - my %layer_paths = (); - my %layer_contact_paths = (); - my %layer_islands = (); - my $process_layer = sub { - my ($layer_id) = @_; - my $layer = $self->layers->[$layer_id]; - - my ($paths, $contact_paths) = ([], []); - my $islands = union_ex([ map @$_, map @$_, $layers{$layer_id}, $layers_contact_areas{$layer_id} ]); - - # make a solid base on bottom layer - if ($layer_id == 0) { - my $filler = $self->fill_maker->filler('rectilinear'); - $filler->angle($Slic3r::Config->support_material_angle + 90); - foreach my $expolygon (@$islands) { - my @paths = $filler->fill_surface( - Slic3r::Surface->new(expolygon => $expolygon), - density => 0.5, - flow_spacing => $self->print->first_layer_support_material_flow->spacing, - ); - my $params = shift @paths; - - push @$paths, map Slic3r::ExtrusionPath->new( - polyline => Slic3r::Polyline->new(@$_), - role => EXTR_ROLE_SUPPORTMATERIAL, - height => undef, - flow_spacing => $params->{flow_spacing}, - ), @paths; - } - } else { - $paths = [ - $clip_pattern->($layer_id, $layers{$layer_id}, $layer->height), - $clip_pattern->($layer_id, $layers_interfaces{$layer_id}, $layer->height, 1), - ]; - $contact_paths = [ $clip_pattern->($layer_id, $layers_contact_areas{$layer_id}, $layer->support_material_contact_height, 1) ]; - } - return ($paths, $contact_paths, $islands); - }; - Slic3r::parallelize( - items => [ keys %layers ], - thread_cb => sub { - my $q = shift; - $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; - my $result = {}; - while (defined (my $layer_id = $q->dequeue)) { - $result->{$layer_id} = [ $process_layer->($layer_id) ]; - } - return $result; - }, - collect_cb => sub { - my $result = shift; - ($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = @{$result->{$_}} for keys %$result; - }, - no_threads_cb => sub { - ($layer_paths{$_}, $layer_contact_paths{$_}, $layer_islands{$_}) = $process_layer->($_) for keys %layers; - }, - ); - foreach my $layer_id (keys %layer_paths) { - my $layer = $self->layers->[$layer_id]; - $layer->support_islands($layer_islands{$layer_id}); - $layer->support_fills(Slic3r::ExtrusionPath::Collection->new); - $layer->support_contact_fills(Slic3r::ExtrusionPath::Collection->new); - push @{$layer->support_fills->paths}, @{$layer_paths{$layer_id}}; - push @{$layer->support_contact_fills->paths}, @{$layer_contact_paths{$layer_id}}; + # interface + if (@{$interface{$layer_id}}) { + $fillers{interface}->angle($interface_angle); + + # steal some space from support + $interface{$layer_id} = intersection( + [ offset($interface{$layer_id}, scale 3) ], + [ @{$interface{$layer_id}}, @{$support{$layer_id}} ], + ); + $support{$layer_id} = diff( + $support{$layer_id}, + $interface{$layer_id}, + ); + + my @paths = (); + foreach my $expolygon (offset_ex($interface{$layer_id}, -$flow->scaled_width/2)) { + my @p = $fillers{interface}->fill_surface( + Slic3r::Surface->new(expolygon => $expolygon), + density => $interface_density, + flow_spacing => $flow->spacing, + complete => 1, + ); + my $params = shift @p; + + push @paths, map Slic3r::ExtrusionPath->pack( + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_SUPPORTMATERIAL, + height => undef, + flow_spacing => $params->{flow_spacing}, + ), @p; + } + $result->{interface} = [ @paths ]; + } + + # support or flange + if (@{$support{$layer_id}}) { + my $filler = $fillers{support}; + $filler->angle($angles[ ($layer_id) % @angles ]); + my $density = $support_density; + my $flow_spacing = $flow->spacing; + + # TODO: use offset2_ex() + my $to_infill = [ offset_ex(union($support{$layer_id}), -$flow->scaled_width/2) ]; + my @paths = (); + + # base flange + if ($layer_id == 0) { + $filler = $fillers{interface}; + $filler->angle($Slic3r::Config->support_material_angle + 90); + $density = 0.5; + $flow_spacing = $self->print->first_layer_support_material_flow->spacing; + } else { + # draw a perimeter all around support infill + # TODO: use brim ordering algorithm + push @paths, map Slic3r::ExtrusionPath->pack( + polyline => $_->split_at_first_point, + role => EXTR_ROLE_SUPPORTMATERIAL, + height => undef, + flow_spacing => $flow->spacing, + ), map @$_, @$to_infill; + + # TODO: use offset2_ex() + $to_infill = [ offset_ex([ map @$_, @$to_infill ], -$flow->scaled_spacing) ]; + } + + foreach my $expolygon (@$to_infill) { + my @p = $filler->fill_surface( + Slic3r::Surface->new(expolygon => $expolygon), + density => $density, + flow_spacing => $flow_spacing, + complete => 1, + ); + my $params = shift @p; + + push @paths, map Slic3r::ExtrusionPath->pack( + polyline => Slic3r::Polyline->new(@$_), + role => EXTR_ROLE_SUPPORTMATERIAL, + height => undef, + flow_spacing => $params->{flow_spacing}, + ), @p; + } + + $result->{support} = [ @paths ]; + } + + # islands + $result->{islands} = union_ex([ + @{$interface{$layer_id} || []}, + @{$support{$layer_id} || []}, + ]); + + return $result; + }; + + my $apply = sub { + my ($layer_id, $result) = @_; + my $layer = $self->support_layers->[$layer_id]; + + my $interface_collection = Slic3r::ExtrusionPath::Collection->new(paths => [ @{$result->{contact}}, @{$result->{interface}} ]); + $layer->support_interface_fills($interface_collection) if @{$interface_collection->paths} > 0; + + my $support_collection = Slic3r::ExtrusionPath::Collection->new(paths => $result->{support}); + $layer->support_fills($support_collection) if @{$support_collection->paths} > 0; + + $layer->support_islands($result->{islands}); + }; + Slic3r::parallelize( + items => [ 0 .. $#{$self->support_layers} ], + thread_cb => sub { + my $q = shift; + $Slic3r::Geometry::Clipper::clipper = Math::Clipper->new; + my $result = {}; + while (defined (my $layer_id = $q->dequeue)) { + $result->{$layer_id} = $process_layer->($layer_id); + } + return $result; + }, + collect_cb => sub { + my $result = shift; + $apply->($_, $result->{$_}) for keys %$result; + }, + no_threads_cb => sub { + $apply->($_, $process_layer->($_)) for 0 .. $#{$self->support_layers}; + }, + ); +} + +sub _compute_support_layers { + my ($contact_z, $top_z, $config, $flow) = @_; + + # quick table to check whether a given Z is a top surface + my %top = map { $_ => 1 } @$top_z; + + # determine layer height for any non-contact layer + # we use max() to prevent many ultra-thin layers to be inserted in case + # layer_height > nozzle_diameter * 0.75 + my $support_material_height = max($config->layer_height, $flow->nozzle_diameter * 0.75); + + my @support_layers = sort { $a <=> $b } @$contact_z, @$top_z, + (map { $_ + $flow->nozzle_diameter } @$top_z); + + # enforce first layer height + my $first_layer_height = $config->get_value('first_layer_height'); + shift @support_layers while @support_layers && $support_layers[0] < $first_layer_height; + unshift @support_layers, $first_layer_height; + + for (my $i = $#support_layers; $i >= 0; $i--) { + my $target_height = $support_material_height; + if ($i > 0 && $top{ $support_layers[$i-1] }) { + $target_height = $flow->nozzle_diameter; + } + + # enforce first layer height + if (($i == 0 && $support_layers[$i] > $target_height + $first_layer_height) + || ($support_layers[$i] - $support_layers[$i-1] > $target_height + Slic3r::Geometry::epsilon)) { + splice @support_layers, $i, 0, ($support_layers[$i] - $target_height); + $i++; } } + + # remove duplicates + { + my %sl = map { $_ => 1 } @support_layers; + @support_layers = sort { $a <=> $b } keys %sl; + } + + return @support_layers; } 1; diff --git a/lib/Slic3r/Test/SectionCut.pm b/lib/Slic3r/Test/SectionCut.pm index 97d76af8b6..0ee1c2b0ff 100644 --- a/lib/Slic3r/Test/SectionCut.pm +++ b/lib/Slic3r/Test/SectionCut.pm @@ -58,7 +58,7 @@ sub export_svg { ); $group->( - filter => sub { $_[0]->support_fills, $_[0]->support_contact_fills }, + filter => sub { $_[0]->isa('Slic3r::Layer::Support') ? ($_[0]->support_fills, $_[0]->support_interface_fills) : () }, style => { 'stroke-width' => 1, 'stroke' => '#444444', @@ -80,7 +80,7 @@ sub _plot { foreach my $object (@{$self->print->objects}) { foreach my $copy (@{$object->copies}) { - foreach my $layer (@{$object->layers}) { + foreach my $layer (@{$object->layers}, @{$object->support_layers}) { # get all ExtrusionPath objects my @paths = map { $_->polyline->translate(@$copy); $_ } diff --git a/slic3r.pl b/slic3r.pl index 4a654b31ec..52becfdd17 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -407,6 +407,8 @@ $j --infill-extruder Extruder to use for infill (1+, default: 1) --support-material-extruder Extruder to use for support material (1+, default: 1) + --support-material-interface-extruder + Extruder to use for support material interface (1+, default: 1) EOF exit ($exit_code || 0); diff --git a/t/support.t b/t/support.t index bee1987f1b..9ed16395aa 100644 --- a/t/support.t +++ b/t/support.t @@ -1,4 +1,4 @@ -use Test::More tests => 1; +use Test::More tests => 13; use strict; use warnings; @@ -7,15 +7,58 @@ BEGIN { use lib "$FindBin::Bin/../lib"; } +use List::Util qw(first); use Slic3r; +use Slic3r::Geometry qw(epsilon); use Slic3r::Test; +{ + my $config = Slic3r::Config->new_from_defaults; + my @contact_z = my @top_z = (); + + my $test = sub { + my $flow = Slic3r::Flow->new(nozzle_diameter => $config->nozzle_diameter->[0], layer_height => $config->layer_height); + my @support_layers = Slic3r::Print::Object::_compute_support_layers(\@contact_z, \@top_z, $config, $flow); + + is $support_layers[0], $config->first_layer_height, + 'first layer height is honored'; + is scalar(grep { $support_layers[$_]-$support_layers[$_-1] <= 0 } 1..$#support_layers), 0, + 'no null or negative support layers'; + is scalar(grep { $support_layers[$_]-$support_layers[$_-1] > $flow->nozzle_diameter + epsilon } 1..$#support_layers), 0, + 'no layers thicker than nozzle diameter'; + + my $wrong_top_spacing = 0; + foreach my $top_z (@top_z) { + # find layer index of this top surface + my $layer_id = first { abs($support_layers[$_] - $top_z) < epsilon } 0..$#support_layers; + + # check that first support layer above this top surface is spaced with nozzle diameter + $wrong_top_spacing = 1 + if ($support_layers[$layer_id+1] - $support_layers[$layer_id]) != $flow->nozzle_diameter; + } + ok !$wrong_top_spacing, 'layers above top surfaces are spaced correctly'; + }; + + $config->set('layer_height', 0.2); + $config->set('first_layer_height', 0.3); + @contact_z = (1.9); + @top_z = (1.1); + $test->(); + + $config->set('first_layer_height', 0.4); + $test->(); + + $config->set('layer_height', $config->nozzle_diameter->[0]); + $test->(); +} + { my $config = Slic3r::Config->new_from_defaults; $config->set('raft_layers', 3); $config->set('brim_width', 6); $config->set('skirts', 0); $config->set('support_material_extruder', 2); + $config->set('support_material_interface_extruder', 2); $config->set('layer_height', 0.4); $config->set('first_layer_height', '100%'); my $print = Slic3r::Test::init_print('20mm_cube', config => $config); From 4e13d61aed23a402ab1139177f55412d6a88a8c0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 30 Jul 2013 12:15:40 +0200 Subject: [PATCH 40/52] Fix retraction/Z/lift problems after recent changes --- lib/Slic3r/GCode.pm | 4 ++-- t/layers.t | 13 +------------ 2 files changed, 3 insertions(+), 14 deletions(-) diff --git a/lib/Slic3r/GCode.pm b/lib/Slic3r/GCode.pm index 1df6e8a794..08c5c89be5 100644 --- a/lib/Slic3r/GCode.pm +++ b/lib/Slic3r/GCode.pm @@ -134,10 +134,9 @@ sub move_z { # this retraction may alter $self->z $gcode .= $self->retract(move_z => $z) if $self->extruder->retract_layer_change; - $self->speed('travel'); $gcode .= $self->G0(undef, $z, 0, $comment || ('move to next layer (' . $self->layer->id . ')')) - unless !defined $current_z || $self->z != $current_z; + if !defined $self->z || abs($z - ($self->z - $self->lifted)) > epsilon; $gcode .= $self->move_z_callback->() if defined $self->move_z_callback; } elsif ($z < $self->z && $z > ($self->z - $self->lifted + epsilon)) { # we're moving to a layer height which is greater than the nominal current one @@ -525,6 +524,7 @@ sub unretract { if ($self->lifted) { $self->speed('travel'); + $gcode .= sprintf ";AAA selfz = %s, lifted = %s\n", $self->z // 'nd', $self->lifted // 'nd'; $gcode .= $self->G0(undef, $self->z - $self->lifted, 0, 'restore layer Z'); $self->lifted(0); } diff --git a/t/layers.t b/t/layers.t index e4fdcb9f9a..6ee92cf2eb 100644 --- a/t/layers.t +++ b/t/layers.t @@ -1,4 +1,4 @@ -use Test::More tests => 5; +use Test::More tests => 4; use strict; use warnings; @@ -56,15 +56,4 @@ ok $test->(), "positive Z offset"; $config->set('z_offset', -0.8); ok $test->(), "negative Z offset"; -{ - my $config = Slic3r::Config->new_from_defaults; - $config->set('nozzle_diameter', [0.35]); - $config->set('layer_height', 0.1333); - - my $print = Slic3r::Test::init_print('2x20x10', config => $config); - $print->init_extruders; - $_->region(0) for @{$print->objects->[0]->layers}; # init layer regions - ok $print->objects->[0]->layers->[1]->support_material_contact_height > 0, 'support_material_contact_height is positive'; -} - __END__ From 1b4878f30560b45318b52160ab8e3c495a75aeeb Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Tue, 30 Jul 2013 15:44:08 +0200 Subject: [PATCH 41/52] Fix hang in new support material caused by numerical approximation leaving small polygons, as usual --- lib/Slic3r/Print/Object.pm | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index d71ff140dc..9aa6ca7ec9 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -974,6 +974,7 @@ sub generate_support_material { [ @{ $top{$z} || [] }, ], + 1, ); } @@ -991,6 +992,7 @@ sub generate_support_material { @{ $top{$z} || [] }, @{ $interface{$i} || [] }, ], + 1, ); } } From dd935e203624deea2c07f78a8d66f106dc4f1905 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 00:01:53 +0200 Subject: [PATCH 42/52] Some comments and minor fixes to admesh code by Andy Doucette --- xs/src/admesh/connect.c | 1 + xs/src/admesh/normals.c | 15 ++++++++++++--- xs/src/admesh/stl_io.c | 1 + 3 files changed, 14 insertions(+), 3 deletions(-) diff --git a/xs/src/admesh/connect.c b/xs/src/admesh/connect.c index 18a137121a..4582bf11f9 100644 --- a/xs/src/admesh/connect.c +++ b/xs/src/admesh/connect.c @@ -82,6 +82,7 @@ stl_check_facets_exact(stl_file *stl) { facet = stl->facet_start[i]; + //If any two of the three vertices are found to be exactally the same, call them degenerate and remove the facet. if( !memcmp(&facet.vertex[0], &facet.vertex[1], sizeof(stl_vertex)) || !memcmp(&facet.vertex[1], &facet.vertex[2], diff --git a/xs/src/admesh/normals.c b/xs/src/admesh/normals.c index baaab36ebd..bbba99e35d 100644 --- a/xs/src/admesh/normals.c +++ b/xs/src/admesh/normals.c @@ -121,9 +121,13 @@ stl_fix_normal_directions(stl_file *stl) facet_num = 0; + //If normal vector is not within tolerance and backwards: + //Arbitrarily starts at face 0. If this one is wrong, we're screwed. Thankfully, the chances + // of it being wrong randomly are low if most of the triangles are right: if(stl_check_normal_vector(stl, 0, 0) == 2) stl_reverse_facet(stl, 0); - + + //Say that we've fixed this facet: norm_sw[facet_num] = 1; /* edge_num = 0; vnot = stl->neighbors_start[0].which_vertex_not[0]; @@ -133,19 +137,24 @@ stl_fix_normal_directions(stl_file *stl) for(;;) { /* Add neighbors_to_list. */ + //Add unconnected neighbors to the list:a for(j = 0; j < 3; j++) { /* Reverse the neighboring facets if necessary. */ if(stl->neighbors_start[facet_num].which_vertex_not[j] > 2) { + // If the facet has a neighbor that is -1, it means that edge isn't shared by another + // facet. if(stl->neighbors_start[facet_num].neighbor[j] != -1) { stl_reverse_facet (stl, stl->neighbors_start[facet_num].neighbor[j]); } } + //If this edge of the facet is connected: if(stl->neighbors_start[facet_num].neighbor[j] != -1) { + //If we haven't fixed this facet yet, add it to the list: if(norm_sw[stl->neighbors_start[facet_num].neighbor[j]] != 1) { /* Add node to beginning of list. */ @@ -170,14 +179,14 @@ stl_fix_normal_directions(stl_file *stl) head->next = head->next->next; free(temp); } - else + else //if we ran out of facets to fix: { /* All of the facets in this part have been fixed. */ stl->stats.number_of_parts += 1; /* There are (checked-checked_before) facets */ /* in part stl->stats.number_of_parts */ checked_before = checked; - if(checked == stl->stats.number_of_facets) + if(checked >= stl->stats.number_of_facets) { /* All of the facets have been checked. Bail out. */ break; diff --git a/xs/src/admesh/stl_io.c b/xs/src/admesh/stl_io.c index 1732bc4ae3..1b2671ab2e 100644 --- a/xs/src/admesh/stl_io.c +++ b/xs/src/admesh/stl_io.c @@ -198,6 +198,7 @@ stl_print_neighbors(stl_file *stl, char *file) stl->neighbors_start[i].neighbor[2], (int)stl->neighbors_start[i].which_vertex_not[2]); } + fclose(fp); } static void From 1479d6933b417e26bf49f39d2b6de3ccee84a600 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 00:32:48 +0200 Subject: [PATCH 43/52] Prevent admesh to reverse all facets twice in some mostly-random situations. Normalizing a null normal should still return a null normal in order to fix it properly later instead of treating it as if it was a true normal and thus reversing the facet (and if that is the first facet, all of the others would be reversed as well). #1362 --- xs/src/admesh/normals.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xs/src/admesh/normals.c b/xs/src/admesh/normals.c index bbba99e35d..f827a712c8 100644 --- a/xs/src/admesh/normals.c +++ b/xs/src/admesh/normals.c @@ -359,7 +359,7 @@ void stl_normalize_vector(float v[]) min_normal_length = 0.000000000001; if(length < min_normal_length) { - v[0] = 1.0; + v[0] = 0.0; v[1] = 0.0; v[2] = 0.0; return; From 3b47e1a492269c07663ea043bb9465202c3bcd7d Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 15:10:11 +0200 Subject: [PATCH 44/52] New --info option to show file info (size, volume, repair stats). Removed utils/file_info.pl --- MANIFEST | 1 - README.markdown | 7 ++++-- lib/Slic3r/GUI/Plater.pm | 2 +- lib/Slic3r/Model.pm | 40 +++++++++++++++++++++++++++++ slic3r.pl | 14 +++++++++-- utils/file_info.pl | 54 ---------------------------------------- 6 files changed, 58 insertions(+), 60 deletions(-) delete mode 100755 utils/file_info.pl diff --git a/MANIFEST b/MANIFEST index ca9cacc020..3abe3b6f1e 100644 --- a/MANIFEST +++ b/MANIFEST @@ -88,7 +88,6 @@ t/support.t t/svg.t t/vibrationlimit.t utils/amf-to-stl.pl -utils/file_info.pl utils/gcode_sectioncut.pl utils/post-processing/filament-weight.pl utils/post-processing/prowl-notification.pl diff --git a/README.markdown b/README.markdown index 46fbd0e635..ad304dc1b9 100644 --- a/README.markdown +++ b/README.markdown @@ -80,7 +80,7 @@ The author of the Silk icon set is Mark James. ## How can I invoke slic3r.pl using the command line? - Usage: slic3r.pl [ OPTIONS ] file.stl + Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... --help Output this usage screen and exit --version Output the version of Slic3r and exit @@ -90,7 +90,10 @@ The author of the Silk icon set is Mark James. -o, --output File to output gcode to (by default, the file will be saved into the same directory as the input file using the --output-filename-format to generate the filename) - --repair Automatically repair given STL files and saves them as _fixed.obj + + Non-slicing actions (no G-code will be generated): + --repair Repair given STL files and save them as _fixed.obj + --info Output information about the supplied file(s) and exit GUI options: --no-plater Disable the plater tab diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index 83fe4d7d4a..cbb5e181bb 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1125,7 +1125,7 @@ sub check_manifoldness { my $self = shift; if ($self->mesh_stats) { - if (first { $self->mesh_stats->{$_} > 0 } qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges)) { + if ($self->get_model_object->needed_repair) { warn "Warning: the input file contains manifoldness errors. " . "Slic3r repaired it successfully by guessing what the correct shape should be, " . "but you might still want to inspect the G-code before printing.\n"; diff --git a/lib/Slic3r/Model.pm b/lib/Slic3r/Model.pm index ba873da9d5..b07125f656 100644 --- a/lib/Slic3r/Model.pm +++ b/lib/Slic3r/Model.pm @@ -278,6 +278,11 @@ sub split_meshes { } } +sub print_info { + my $self = shift; + $_->print_info for @{$self->objects}; +} + package Slic3r::Model::Region; use Moo; @@ -287,6 +292,7 @@ has 'attributes' => (is => 'rw', default => sub { {} }); package Slic3r::Model::Object; use Moo; +use File::Basename qw(basename); use List::Util qw(first); use Slic3r::Geometry qw(X Y Z MIN MAX move_points move_points_3D); use Storable qw(dclone); @@ -396,6 +402,7 @@ sub scale { } $self->_bounding_box->scale($factor) if defined $self->_bounding_box; + $self->mesh_stats->{volume} *= ($factor**3) if defined $self->mesh_stats; } sub rotate { @@ -425,6 +432,39 @@ sub check_manifoldness { return (first { !$_->mesh->check_manifoldness } @{$self->volumes}) ? 0 : 1; } +sub needed_repair { + my $self = shift; + + return $self->mesh_stats + && first { $self->mesh_stats->{$_} > 0 } + qw(degenerate_facets edges_fixed facets_removed facets_added facets_reversed backwards_edges); +} + +sub print_info { + my $self = shift; + + printf "Info about %s:\n", basename($self->input_file); + printf " size: x=%.3f y=%.3f z=%.3f\n", @{$self->size}; + if (my $stats = $self->mesh_stats) { + printf " number of facets: %d\n", $stats->{number_of_facets}; + printf " number of shells: %d\n", $stats->{number_of_parts}; + printf " volume: %.3f\n", $stats->{volume}; + if ($self->needed_repair) { + printf " needed repair: yes\n"; + printf " degenerate facets: %d\n", $stats->{degenerate_facets}; + printf " edges fixed: %d\n", $stats->{edges_fixed}; + printf " facets removed: %d\n", $stats->{facets_removed}; + printf " facets added: %d\n", $stats->{facets_added}; + printf " facets reversed: %d\n", $stats->{facets_reversed}; + printf " backwards edges: %d\n", $stats->{backwards_edges}; + } else { + printf " needed repair: no\n"; + } + } else { + printf " number of facets: %d\n", scalar(map @{$_->facets}, @{$self->volumes}); + } +} + sub clone { dclone($_[0]) } package Slic3r::Model::Volume; diff --git a/slic3r.pl b/slic3r.pl index 52becfdd17..b96b112ed1 100755 --- a/slic3r.pl +++ b/slic3r.pl @@ -34,6 +34,7 @@ my %cli_options = (); 'export-svg' => \$opt{export_svg}, 'merge|m' => \$opt{merge}, 'repair' => \$opt{repair}, + 'info' => \$opt{info}, ); foreach my $opt_key (keys %{$Slic3r::Config::Options}) { my $cli = $Slic3r::Config::Options->{$opt_key}->{cli} or next; @@ -120,6 +121,11 @@ if (@ARGV) { # slicing from command line $_->rotate($config->rotate) for @{$model->objects}; $model->arrange_objects($config); + if ($opt{info}) { + $model->print_info; + next; + } + my $print = Slic3r::Print->new(config => $config); $print->add_model($model); $print->validate; @@ -156,7 +162,7 @@ EOF Slic3r $Slic3r::VERSION is a STL-to-GCODE translator for RepRap 3D printers written by Alessandro Ranellucci - http://slic3r.org/ -Usage: slic3r.pl [ OPTIONS ] file.stl +Usage: slic3r.pl [ OPTIONS ] [ file.stl ] [ file2.stl ] ... --help Output this usage screen and exit --version Output the version of Slic3r and exit @@ -166,7 +172,11 @@ Usage: slic3r.pl [ OPTIONS ] file.stl -o, --output File to output gcode to (by default, the file will be saved into the same directory as the input file using the --output-filename-format to generate the filename) - --repair Automatically repair given STL files and saves them as _fixed.obj + + Non-slicing actions (no G-code will be generated): + --repair Repair given STL files and save them as _fixed.obj + --info Output information about the supplied file(s) and exit + $j GUI options: --no-plater Disable the plater tab diff --git a/utils/file_info.pl b/utils/file_info.pl deleted file mode 100755 index 44063ab70b..0000000000 --- a/utils/file_info.pl +++ /dev/null @@ -1,54 +0,0 @@ -#!/usr/bin/perl -# This script reads a file and outputs information about it - -use strict; -use warnings; - -BEGIN { - use FindBin; - use lib "$FindBin::Bin/../lib"; -} - -use File::Basename qw(basename); -use Getopt::Long qw(:config no_auto_abbrev); -use Slic3r; -$|++; - -my %opt = (); -{ - my %options = ( - 'help' => sub { usage() }, - ); - GetOptions(%options) or usage(1); - $ARGV[0] or usage(1); -} - -{ - my $input_file = $ARGV[0]; - die "This script doesn't support AMF yet\n" if $input_file =~ /\.amf$/i; - - my $model; - $model = Slic3r::Format::STL->read_file($input_file) if $input_file =~ /\.stl$/i; - die "Unable to read file\n" if !$model; - - printf "Info about %s:\n", basename($input_file); - my $mesh = $model->mesh; - $mesh->check_manifoldness; - printf " number of facets: %d\n", scalar @{$mesh->facets}; - printf " size: x=%s y=%s z=%s\n", @{$mesh->size}; -} - - -sub usage { - my ($exit_code) = @_; - - print <<"EOF"; -Usage: file_info.pl [ OPTIONS ] file.stl - - --help Output this usage screen and exit - -EOF - exit ($exit_code || 0); -} - -__END__ From 60e5e2166a56a144be73a5dac6fb547884d474e1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 16:29:44 +0200 Subject: [PATCH 45/52] Include support contact layers in skirt/brim generation --- lib/Slic3r/Print.pm | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index d12a6e87f4..a8a81cb103 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -591,7 +591,8 @@ sub make_skirt { if (@{ $object->support_layers }) { my @support_layers = map $object->support_layers->[$_], 0..min($Slic3r::Config->skirt_height-1, $#{$object->support_layers}); push @layer_points, - (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @support_layers); + (map @{$_->unpack->polyline}, map @{$_->support_fills->paths}, grep $_->support_fills, @support_layers), + (map @{$_->unpack->polyline}, map @{$_->support_interface_fills->paths}, grep $_->support_interface_fills, @support_layers); } push @points, map move_points($_, @layer_points), @{$object->copies}; } @@ -659,6 +660,9 @@ sub make_brim { push @object_islands, (map $_->unpack->polyline->grow($grow_distance), @{$support_layer0->support_fills->paths}) if $support_layer0->support_fills; + push @object_islands, + (map $_->unpack->polyline->grow($grow_distance), @{$support_layer0->support_interface_fills->paths}) + if $support_layer0->support_interface_fills; } foreach my $copy (@{$object->copies}) { push @islands, map $_->clone->translate(@$copy), @object_islands; From 415a2d165aeb6c297977f8505454fe86af866801 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 18:55:23 +0200 Subject: [PATCH 46/52] Cleaner code for first layer temperatures --- lib/Slic3r/Print.pm | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index a8a81cb103..e30a5ed7f2 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -737,21 +737,26 @@ sub write_gcode { print $fh "G21 ; set units to millimeters\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; print $fh $gcodegen->set_fan(0, 1) if $Slic3r::Config->cooling && $Slic3r::Config->disable_fan_first_layers; - # write start commands to file - printf $fh $gcodegen->set_bed_temperature($Slic3r::Config->first_layer_bed_temperature, 1), - if $Slic3r::Config->first_layer_bed_temperature && $Slic3r::Config->start_gcode !~ /M(?:190|140)/i; + # set bed temperature + if ((my $temp = $Slic3r::Config->first_layer_bed_temperature) && $Slic3r::Config->start_gcode !~ /M(?:190|140)/i) { + printf $fh $gcodegen->set_bed_temperature($temp, 1); + } + + # set extruder(s) temperature before and after start G-code my $print_first_layer_temperature = sub { - for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { - printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 0, $t) - if $self->extruders->[$t]->first_layer_temperature; + my ($wait) = @_; + + return if $Slic3r::Config->start_gcode =~ /M(?:109|104)/i; + for my $t (0 .. $#{$self->extruders}) { + my $temp = $self->extruders->[$t]->first_layer_temperature; + printf $fh $gcodegen->set_temperature($temp, $wait, $t) if $temp > 0; } }; - $print_first_layer_temperature->() if $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; + $print_first_layer_temperature->(0); printf $fh "%s\n", $Slic3r::Config->replace_options($Slic3r::Config->start_gcode); - for my $t (grep $self->extruders->[$_], 0 .. $#{$Slic3r::Config->first_layer_temperature}) { - printf $fh $gcodegen->set_temperature($self->extruders->[$t]->first_layer_temperature, 1, $t) - if $self->extruders->[$t]->first_layer_temperature && $Slic3r::Config->start_gcode !~ /M(?:109|104)/i; - } + $print_first_layer_temperature->(1); + + # set other general things print $fh "G90 ; use absolute coordinates\n" if $Slic3r::Config->gcode_flavor ne 'makerware'; if ($Slic3r::Config->gcode_flavor =~ /^(?:reprap|teacup)$/) { printf $fh $gcodegen->reset_e; From 6ddeb2fa071ecc94b24ee2ddcdcf544ccdfe3e9e Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 19:52:25 +0200 Subject: [PATCH 47/52] Initialize support material interface extruder explicitely. #1364 --- lib/Slic3r/Print.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Print.pm b/lib/Slic3r/Print.pm index e30a5ed7f2..5bcc4b92a3 100644 --- a/lib/Slic3r/Print.pm +++ b/lib/Slic3r/Print.pm @@ -228,7 +228,7 @@ sub init_extruders { # initialize all extruder(s) we need my @used_extruders = ( 0, - (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material)), + (map $self->config->get("${_}_extruder")-1, qw(perimeter infill support_material support_material_interface)), (values %extruder_mapping), ); for my $extruder_id (keys %{{ map {$_ => 1} @used_extruders }}) { From d83b14655a84567c279ac94178504b11f9276e76 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 20:02:24 +0200 Subject: [PATCH 48/52] Better tooltip for raft layers --- lib/Slic3r/Config.pm | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/Slic3r/Config.pm b/lib/Slic3r/Config.pm index b45ef47ee2..0d89b79ee2 100644 --- a/lib/Slic3r/Config.pm +++ b/lib/Slic3r/Config.pm @@ -703,7 +703,7 @@ our $Options = { }, 'raft_layers' => { label => 'Raft layers', - tooltip => 'Number of total raft layers to insert below the object(s).', + tooltip => 'The object will be raised by this number of layers, and support material will be generated under it.', sidetext => 'layers', cli => 'raft-layers=i', type => 'i', From 528595c7f7d478e73c1d6d65c75681449bcb3265 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 20:42:24 +0200 Subject: [PATCH 49/52] Automatically ignore per-role extruders if they're not configured in Printer Settings (only in GUI expert mode) - CLI mode continues to autogenerate extruder settings. #1236 --- lib/Slic3r/GUI/SkeinPanel.pm | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/Slic3r/GUI/SkeinPanel.pm b/lib/Slic3r/GUI/SkeinPanel.pm index 5f00543746..a76137f821 100644 --- a/lib/Slic3r/GUI/SkeinPanel.pm +++ b/lib/Slic3r/GUI/SkeinPanel.pm @@ -4,6 +4,7 @@ use warnings; use utf8; use File::Basename qw(basename dirname); +use List::Util qw(min); use Slic3r::Geometry qw(X Y); use Wx qw(:dialog :filedialog :font :icon :id :misc :notebook :panel :sizer); use Wx::Event qw(EVT_BUTTON); @@ -404,6 +405,10 @@ sub config { $config->set('first_layer_height', $config->nozzle_diameter->[0]); $config->set('avoid_crossing_perimeters', 1); $config->set('infill_every_layers', 10); + } else { + my $extruders_count = $self->{options_tabs}{printer}{extruders_count}; + $config->set("${_}_extruder", min($config->get("${_}_extruder"), $extruders_count)) + for qw(perimeter infill support_material support_material_interface); } return $config; From d8e2cde9625d75d6f78774e0366b60eb88b49e4f Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Wed, 31 Jul 2013 23:44:17 +0200 Subject: [PATCH 50/52] Avoid problems caused in support material by layer heights being specified in configuration without the leading 0 (like .25). #1366 --- lib/Slic3r/Print/Object.pm | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/Slic3r/Print/Object.pm b/lib/Slic3r/Print/Object.pm index 9aa6ca7ec9..2ca0e0d176 100644 --- a/lib/Slic3r/Print/Object.pm +++ b/lib/Slic3r/Print/Object.pm @@ -908,6 +908,10 @@ sub generate_support_material { my $contact_z = $layer->print_z - $nozzle_diameter * 1.5; ###$contact_z = $layer->print_z - $layer->height; + + # ignore this contact area if it's too low + next if $contact_z < $Slic3r::Config->first_layer_height; + $contact{$contact_z} = [ @contact ]; $overhang{$contact_z} = [ @overhang ]; } @@ -1227,7 +1231,7 @@ sub _compute_support_layers { # enforce first layer height my $first_layer_height = $config->get_value('first_layer_height'); - shift @support_layers while @support_layers && $support_layers[0] < $first_layer_height; + shift @support_layers while @support_layers && $support_layers[0] <= $first_layer_height; unshift @support_layers, $first_layer_height; for (my $i = $#support_layers; $i >= 0; $i--) { @@ -1244,9 +1248,9 @@ sub _compute_support_layers { } } - # remove duplicates + # remove duplicates and make sure all 0.x values have the leading 0 { - my %sl = map { $_ => 1 } @support_layers; + my %sl = map { 1 * $_ => 1 } @support_layers; @support_layers = sort { $a <=> $b } keys %sl; } From 0ce7ebc4b8a63929806bf6480574da0258c313f0 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Aug 2013 20:21:08 +0200 Subject: [PATCH 51/52] Integerize plater thumbnails --- lib/Slic3r/GUI/Plater.pm | 3 +++ lib/Slic3r/TriangleMesh.pm | 13 +++++++------ 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index cbb5e181bb..a6fb6cf2e7 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1174,6 +1174,9 @@ sub make_thumbnail { ); # Note: the call to simplify() was removed here because it used Clipper # simplification which needs integerization. + # TODO: remove polygons with area <= 1 pixel + + $thumbnail->scale(&Slic3r::SCALING_FACTOR); $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object; diff --git a/lib/Slic3r/TriangleMesh.pm b/lib/Slic3r/TriangleMesh.pm index bebce77169..c2e543467e 100644 --- a/lib/Slic3r/TriangleMesh.pm +++ b/lib/Slic3r/TriangleMesh.pm @@ -3,7 +3,7 @@ use Moo; use List::Util qw(reduce min max first); use Slic3r::Geometry qw(X Y Z A B unscale same_point); -use Slic3r::Geometry::Clipper qw(union_ex); +use Slic3r::Geometry::Clipper qw(union_ex offset); use Storable; # public @@ -550,19 +550,20 @@ sub split_mesh { return @meshes; } +# this will return *scaled* expolygons, so it is expected to be run +# on unscaled meshes sub horizontal_projection { my $self = shift; my @f = (); foreach my $facet (@{$self->facets}) { - push @f, Slic3r::Polygon->new(map [ @{$self->vertices->[$_]}[X,Y] ], @$facet); + push @f, Slic3r::Polygon->new( + map [ map $_ / &Slic3r::SCALING_FACTOR, @{$self->vertices->[$_]}[X,Y] ], @$facet + ); } - my $scale_vector = Math::Clipper::integerize_coordinate_sets({ bits => 32 }, @f); $_->make_counter_clockwise for @f; # do this after scaling, as winding order might change while doing that - my $union = union_ex([ Slic3r::Geometry::Clipper::offset(\@f, 10000) ]); - Math::Clipper::unscale_coordinate_sets($scale_vector, $_) for @$union; - return $union; + return union_ex(\@f, 1); } 1; From 4438aec12ce3b90b6a4ffde680a0d2b6dc1ed7d1 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 5 Aug 2013 20:48:09 +0200 Subject: [PATCH 52/52] Revert "Remove thumbnail simplification because it caused loss of very thin parts. #1327" This reverts commit 1210b8989350b96ffbb0ddda39c6132e3eb3f40f. Conflicts: lib/Slic3r/GUI/Plater.pm --- lib/Slic3r/ExPolygon.pm | 5 +++++ lib/Slic3r/GUI/Plater.pm | 24 +++++++++++++++--------- 2 files changed, 20 insertions(+), 9 deletions(-) diff --git a/lib/Slic3r/ExPolygon.pm b/lib/Slic3r/ExPolygon.pm index 3db8a7241a..84afc9109d 100644 --- a/lib/Slic3r/ExPolygon.pm +++ b/lib/Slic3r/ExPolygon.pm @@ -325,6 +325,11 @@ use Slic3r::Geometry qw(X1 Y1); has 'expolygons' => (is => 'ro', default => sub { [] }); +sub append { + my $self = shift; + push @{$self->expolygons}, @_; +} + sub clone { my $self = shift; return (ref $self)->new( diff --git a/lib/Slic3r/GUI/Plater.pm b/lib/Slic3r/GUI/Plater.pm index a6fb6cf2e7..57ad8e8d31 100644 --- a/lib/Slic3r/GUI/Plater.pm +++ b/lib/Slic3r/GUI/Plater.pm @@ -1167,17 +1167,23 @@ sub make_thumbnail { my $self = shift; my $mesh = $self->model_object->mesh; # $self->model_object is already aligned to origin - my $thumbnail = Slic3r::ExPolygon::Collection->new( - expolygons => (@{$mesh->facets} <= 5000) - ? $mesh->horizontal_projection - : [ Slic3r::ExPolygon->new($self->convex_hull) ], - ); - # Note: the call to simplify() was removed here because it used Clipper - # simplification which needs integerization. - # TODO: remove polygons with area <= 1 pixel + my $thumbnail = Slic3r::ExPolygon::Collection->new; + if (@{$mesh->facets} <= 5000) { + $thumbnail->append(@{ $mesh->horizontal_projection }); + } else { + my $convex_hull = Slic3r::ExPolygon->new($self->convex_hull)->clone; + $convex_hull->scale(1/&Slic3r::SCALING_FACTOR); + $thumbnail->append($convex_hull); + } + + # remove polygons with area <= 1mm + my $area_threshold = Slic3r::Geometry::scale 1; + @{$thumbnail->expolygons} = + map $_->simplify(0.5), + grep $_->area >= $area_threshold, + @{$thumbnail->expolygons}; $thumbnail->scale(&Slic3r::SCALING_FACTOR); - $self->thumbnail($thumbnail); # ignored in multi-threaded environments $self->free_model_object;