From 913f401280130831cdd3d2315b864b6ab60043c8 Mon Sep 17 00:00:00 2001 From: Alessandro Ranellucci Date: Mon, 29 Jul 2013 20:49:54 +0200 Subject: [PATCH] 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 3d1ef2848..46fbd0e63 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 03b568339..b45ef47ee 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 a4f44fe26..45283bef6 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 af296edf0..307ab0e32 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 27ff3c56e..70fd3962a 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 35d13c874..fc8d35b8a 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 ece506253..d1bc2818b 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 fd4bd571f..0196cf677 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 49b25ccdd..d12a6e87f 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 8bd80d151..d71ff140d 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 97d76af8b..0ee1c2b0f 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 4a654b31e..52becfdd1 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 bee1987f1..9ed16395a 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);